Merge "Backport system default callback to Q"
diff --git a/Android.bp b/Android.bp
index d9d84c0..dd2d391 100644
--- a/Android.bp
+++ b/Android.bp
@@ -220,6 +220,7 @@
     libs: ["unsupportedappusage"],
     static_libs: [
         "androidx.annotation_annotation",
+        "modules-utils-build_system",
         "netd_aidl_interface-lateststable-java",
         "netlink-client",
         "networkstack-client",
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 54f4b22..2230549 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -21,18 +21,29 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// TODO: remove this filegroup together with services.net
 filegroup {
     name: "net-module-utils-srcs",
     srcs: [
+        "src/android/net/shared/NetdUtils.java",
+        "src/android/net/shared/RouteUtils.java",
+        "src/android/net/util/InterfaceParams.java",
+        "src/android/net/util/SharedLog.java",
+    ],
+    visibility: [
+        "//frameworks/base/services/net",
+    ]
+}
+
+filegroup {
+    name: "connectivity-module-utils-srcs",
+    srcs: [
         "src/android/net/util/SharedLog.java",
         "src/android/net/shared/NetdUtils.java",
         "src/android/net/shared/NetworkMonitorUtils.java",
         "src/android/net/shared/RouteUtils.java",
-        "src/android/net/util/InterfaceParams.java",
     ],
     visibility: [
-        "//frameworks/base/services/net",
-        "//frameworks/base/packages/Connectivity/service",
         "//packages/modules/Connectivity/service",
     ]
 }
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
index 18138a7..0cd9f65 100644
--- a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -28,6 +28,8 @@
 
 import android.net.NetworkCapabilities;
 
+import com.android.modules.utils.build.SdkLevel;
+
 /** @hide */
 public class NetworkMonitorUtils {
     // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
@@ -68,7 +70,8 @@
     public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
         if (nc == null) return false;
 
-        final boolean isVcnManaged = !nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        final boolean isVcnManaged = SdkLevel.isAtLeastS()
+                && !nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         final boolean isOemPaid = nc.hasCapability(NET_CAPABILITY_OEM_PAID)
                 && nc.hasCapability(NET_CAPABILITY_TRUSTED);
         final boolean isDefaultCapable = nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index face72c..89c70a9 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -20,6 +20,8 @@
 import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
+import static android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -43,8 +45,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.net.module.util.DeviceConfigUtils;
 import com.android.networkstack.R;
 
 import java.io.PrintWriter;
@@ -143,6 +149,8 @@
     protected static final int MIN_NUD_SOLICIT_NUM = 5;
     protected static final int MAX_NUD_SOLICIT_INTERVAL_MS = 1000;
     protected static final int MIN_NUD_SOLICIT_INTERVAL_MS = 750;
+    protected static final int NUD_MCAST_RESOLICIT_NUM = 3;
+    private static final int INVALID_NUD_MCAST_RESOLICIT_NUM = -1;
 
     public interface Callback {
         /**
@@ -161,6 +169,7 @@
     interface Dependencies {
         void acquireWakeLock(long durationMs);
         IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
+        boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled);
 
         static Dependencies makeDefault(Context context, String iface) {
             final String lockName = TAG + "." + iface;
@@ -176,6 +185,12 @@
                         NeighborEventConsumer cb) {
                     return new IpNeighborMonitor(h, log, cb);
                 }
+
+                public boolean isFeatureEnabled(final Context context, final String name,
+                        boolean defaultEnabled) {
+                    return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
+                            defaultEnabled);
+                }
             };
         }
     }
@@ -183,7 +198,6 @@
     private final InterfaceParams mInterfaceParams;
     private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
-    private final Callback mCallback;
     private final Dependencies mDependencies;
     private final boolean mUsingMultinetworkPolicyTracker;
     private final ConnectivityManager mCm;
@@ -196,6 +210,8 @@
     private volatile long mLastProbeTimeMs;
     private int mNumSolicits;
     private int mInterSolicitIntervalMs;
+    @NonNull
+    private final Callback mCallback;
 
     public IpReachabilityMonitor(
             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
@@ -225,7 +241,10 @@
         // In case the overylaid parameters specify an invalid configuration, set the parameters
         // to the hardcoded defaults first, then set them to the values used in the steady state.
         try {
-            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS);
+            int numResolicits = isMulticastResolicitEnabled()
+                    ? NUD_MCAST_RESOLICIT_NUM
+                    : INVALID_NUD_MCAST_RESOLICIT_NUM;
+            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
         } catch (Exception e) {
             Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
         }
@@ -241,10 +260,12 @@
                     // TODO: Consider what to do with other states that are not within
                     // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
                     if (event.nudState == StructNdMsg.NUD_FAILED) {
+                        // 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);
                     } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
-                        maybeRestoreNeighborParameters();
+                        handleNeighborReachable(prev, event);
                     }
                 });
         mIpNeighborMonitor.start();
@@ -296,6 +317,26 @@
         return false;
     }
 
+    private boolean hasDefaultRouterNeighborMacAddressChanged(
+            @Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) {
+        if (prev == null || !isNeighborDefaultRouter(event)) return false;
+        return !event.macAddr.equals(prev.macAddr);
+    }
+
+    private boolean isNeighborDefaultRouter(@NonNull final NeighborEvent event) {
+        // For the IPv6 link-local scoped address, equals() works because the NeighborEvent.ip
+        // doesn't have a scope id and Inet6Address#equals doesn't consider scope id neither.
+        for (RouteInfo route : mLinkProperties.getRoutes()) {
+            if (route.isDefaultRoute() && event.ip.equals(route.getGateway())) return true;
+        }
+        return false;
+    }
+
+    private boolean isMulticastResolicitEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+                false /* defaultEnabled */);
+    }
+
     public void updateLinkProperties(LinkProperties lp) {
         if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
@@ -333,6 +374,24 @@
         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
     }
 
+    private void handleNeighborReachable(@Nullable final NeighborEvent prev,
+            @NonNull final NeighborEvent event) {
+        if (isMulticastResolicitEnabled()
+                && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
+            // This implies device has confirmed the neighbor's reachability from
+            // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
+            // address hasn't changed is required. If Mac address does change, then
+            // trigger a new neighbor lost event and disconnect.
+            final String logMsg = "ALERT neighbor: " + event.ip
+                    + " MAC address changed from: " + prev.macAddr
+                    + " to: " + event.macAddr;
+            mLog.w(logMsg);
+            mCallback.notifyLost(event.ip, logMsg);
+            return;
+        }
+        maybeRestoreNeighborParameters();
+    }
+
     private void handleNeighborLost(NeighborEvent event) {
         final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
 
@@ -370,11 +429,9 @@
         if (lostProvisioning) {
             final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
             Log.w(TAG, logMsg);
-            if (mCallback != null) {
-                // TODO: remove |ip| when the callback signature no longer has
-                // an InetAddress argument.
-                mCallback.notifyLost(ip, logMsg);
-            }
+            // TODO: remove |ip| when the callback signature no longer has
+            // an InetAddress argument.
+            mCallback.notifyLost(ip, logMsg);
         }
         logNudFailed(lostProvisioning);
     }
@@ -450,6 +507,12 @@
 
     private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs)
             throws RemoteException, IllegalArgumentException {
+        // Do not set mcast_resolicit param by default.
+        setNeighborParameters(numSolicits, interSolicitIntervalMs, INVALID_NUD_MCAST_RESOLICIT_NUM);
+    }
+
+    private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs,
+            int numResolicits) throws RemoteException, IllegalArgumentException {
         Preconditions.checkArgument(numSolicits >= MIN_NUD_SOLICIT_NUM,
                 "numSolicits must be at least " + MIN_NUD_SOLICIT_NUM);
         Preconditions.checkArgument(numSolicits <= MAX_NUD_SOLICIT_NUM,
@@ -464,6 +527,10 @@
                     Integer.toString(interSolicitIntervalMs));
             mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "ucast_solicit",
                     Integer.toString(numSolicits));
+            if (numResolicits != INVALID_NUD_MCAST_RESOLICIT_NUM) {
+                mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "mcast_resolicit",
+                        Integer.toString(numResolicits));
+            }
         }
 
         mNumSolicits = numSolicits;
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 81d0c08..e06cdca 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -255,6 +255,13 @@
      */
     public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra";
 
+    /**
+     * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
+     * set it to 3 by default.
+     */
+    public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
+            "ip_reachability_mcast_resolicit_version";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 630b05d..2b00fb1 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -27,6 +27,8 @@
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS;
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.ip.IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM;
+import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM;
 import static android.net.ipmemorystore.Status.SUCCESS;
 import static android.system.OsConstants.ETH_P_IPV6;
 import static android.system.OsConstants.IFA_F_TEMPORARY;
@@ -49,6 +51,7 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
 import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
@@ -474,6 +477,11 @@
                         NeighborEventConsumer cb) {
                     return new IpNeighborMonitor(h, log, cb);
                 }
+
+                public boolean isFeatureEnabled(final Context context, final String name,
+                        boolean defaultEnabled) {
+                    return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled);
+                }
             };
         }
 
@@ -3094,7 +3102,50 @@
         assertNeighborSolicitation(ns, target);
     }
 
+    private void assertMulticastNeighborSolicitation(final NeighborSolicitation ns,
+            final Inet6Address target) {
+        final MacAddress etherMulticast =
+                NetworkStackUtils.ipv6MulticastToEthernetMulticast(ns.ipv6Hdr.dstIp);
+        assertEquals(etherMulticast, ns.ethHdr.dstMac);
+        assertTrue(ns.ipv6Hdr.dstIp.isMulticastAddress());
+        assertNeighborSolicitation(ns, target);
+    }
+
+    private NeighborSolicitation waitForUnicastNeighborSolicitation(final MacAddress dstMac,
+            final Inet6Address dstIp, final Inet6Address targetIp) throws Exception {
+        NeighborSolicitation ns;
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Filter out the NSes used for duplicate address detetction, the target address
+            // is the global IPv6 address inside these NSes.
+            if (ns.nsHdr.target.isLinkLocalAddress()) break;
+        }
+        assertNotNull("No unicast Neighbor solicitation received on interface within timeout", ns);
+        assertUnicastNeighborSolicitation(ns, dstMac, dstIp, targetIp);
+        return ns;
+    }
+
+    private List<NeighborSolicitation> waitForMultipleNeighborSolicitations() throws Exception {
+        NeighborSolicitation ns;
+        final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Filter out the NSes used for duplicate address detetction, the target address
+            // is the global IPv6 address inside these NSes.
+            if (ns.nsHdr.target.isLinkLocalAddress()) {
+                nsList.add(ns);
+            }
+        }
+        assertFalse(nsList.isEmpty());
+        return nsList;
+    }
+
+    // Override this function with disabled experiment flag by default, in order not to
+    // affect those tests which are just related to basic IpReachabilityMonitor infra.
     private void prepareIpReachabilityMonitorTest() throws Exception {
+        prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */);
+    }
+
+    private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
+            throws Exception {
         final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -3103,6 +3154,8 @@
                 .withDisplayName(TEST_DEFAULT_SSID)
                 .withoutIPv4()
                 .build();
+        setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+                isMulticastResolicitEnabled);
         startIpClientProvisioning(config);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
         doIpv6OnlyProvisioning();
@@ -3115,16 +3168,8 @@
     public void testIpReachabilityMonitor_probeFailed() throws Exception {
         prepareIpReachabilityMonitorTest();
 
-        NeighborSolicitation packet;
-        final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
-        while ((packet = getNextNeighborSolicitation()) != null) {
-            // Filter out the NSes used for duplicate address detetction, the target address
-            // is the global IPv6 address inside these NSes.
-            if (packet.nsHdr.target.isLinkLocalAddress()) {
-                nsList.add(packet);
-            }
-        }
-        assertEquals(IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM, nsList.size());
+        final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+        assertEquals(MIN_NUD_SOLICIT_NUM, nsList.size());
         for (NeighborSolicitation ns : nsList) {
             assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
                     ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -3136,13 +3181,7 @@
     public void testIpReachabilityMonitor_probeReachable() throws Exception {
         prepareIpReachabilityMonitorTest();
 
-        NeighborSolicitation ns;
-        while ((ns = getNextNeighborSolicitation()) != null) {
-            // Filter out the NSes used for duplicate address detetction, the target address
-            // is the global IPv6 address inside these NSes.
-            if (ns.nsHdr.target.isLinkLocalAddress()) break;
-        }
-        assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
                 ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
 
         // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
@@ -3153,4 +3192,61 @@
         mPacketReader.sendResponse(na);
         assertNeverNotifyNeighborLost();
     }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeFailed() throws Exception {
+        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)) {
+            assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+                    ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        for (NeighborSolicitation ns : nsList.subList(MIN_NUD_SOLICIT_NUM, nsList.size())) {
+            assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithSameLinkLayerAddress()
+            throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+        final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNeverNotifyNeighborLost();
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithDiffLinkLayerAddress()
+            throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement with a different link-layer address and check notifyLost
+        // callback will be triggered. Override flag must be set, which indicates that the
+        // advertisement should override an existing cache entry and update the cached link-layer
+        // address, otherwise, kernel won't transit to REACHABLE state with a different link-layer
+        // address.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+                | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+        final MacAddress newMac = MacAddress.fromString("00:1a:11:22:33:55");
+        final ByteBuffer na = NeighborAdvertisement.build(newMac /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
 }
diff --git a/tests/unit/lint-baseline.xml b/tests/unit/lint-baseline.xml
index 0812c2d..0bfcaa9 100644
--- a/tests/unit/lint-baseline.xml
+++ b/tests/unit/lint-baseline.xml
@@ -78,81 +78,4 @@
             column="14"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities())"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="59"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities())"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="71"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val meteredNc = NetworkCapabilities()"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="108"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED)"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="109"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val netCaps = NetworkCapabilities().addTransportType(CELLULAR)"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="130"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI)"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="152"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="            &quot;CapabilitiesChanged&quot; -> cb.onCapabilitiesChanged(net, NetworkCapabilities())"
-        errorLine2="                                                                   ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="279"
-            column="68"/>
-    </issue>
-
 </issues>
diff --git a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
index 5716803..6a84a85 100644
--- a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
@@ -17,21 +17,36 @@
 package android.net.netlink;
 
 import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
+import static android.system.OsConstants.EACCES;
 import static android.system.OsConstants.NETLINK_ROUTE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.net.netlink.NetlinkSocket;
 import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNlMsgHdr;
+import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.Os;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
 import libcore.io.IoUtils;
 
 import org.junit.Test;
@@ -47,7 +62,7 @@
     private final String TAG = "NetlinkSocketTest";
 
     @Test
-    public void testBasicWorkingGetNeighborsQuery() throws Exception {
+    public void testGetNeighborsQuery() throws Exception {
         final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
         assertNotNull(fd);
 
@@ -63,6 +78,25 @@
         assertNotNull(req);
 
         final long TIMEOUT = 500;
+        final Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
+        final int targetSdk =
+                ctx.getPackageManager()
+                        .getApplicationInfo(ctx.getPackageName(), 0)
+                        .targetSdkVersion;
+
+        // Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages
+        if (SdkLevel.isAtLeastT() && targetSdk > 31) {
+            try {
+                NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT);
+                fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms");
+            } catch (ErrnoException e) {
+                // Expected
+                assertEquals(e.errno, EACCES);
+                return;
+            }
+        }
+
+        // Check that apps targeting lower API levels / running on older platforms succeed
         assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
 
         int neighMessageCount = 0;
@@ -103,4 +137,105 @@
 
         IoUtils.closeQuietly(fd);
     }
+
+    @Test
+    public void testBasicWorkingGetAddrQuery() throws Exception {
+        final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
+        assertNotNull(fd);
+
+        NetlinkSocket.connectToKernel(fd);
+
+        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+        assertNotNull(localAddr);
+        assertEquals(0, localAddr.getGroupsMask());
+        assertTrue(0 != localAddr.getPortId());
+
+        final int testSeqno = 8;
+        final byte[] req = newGetAddrRequest(testSeqno);
+        assertNotNull(req);
+
+        final long timeout = 500;
+        assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, timeout));
+
+        int addrMessageCount = 0;
+
+        while (true) {
+            ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, timeout);
+            assertNotNull(response);
+            assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
+            assertEquals(0, response.position());
+            assertEquals(ByteOrder.nativeOrder(), response.order());
+
+            final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(response);
+            assertNotNull(nlmsghdr);
+
+            if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+                break;
+            }
+
+            assertEquals(NetlinkConstants.RTM_NEWADDR, nlmsghdr.nlmsg_type);
+            assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
+            assertEquals(testSeqno, nlmsghdr.nlmsg_seq);
+            assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid);
+            addrMessageCount++;
+
+            final IfaddrMsg ifaMsg = Struct.parse(IfaddrMsg.class, response);
+            assertTrue(
+                    "Non-IP address family: " + ifaMsg.family,
+                    ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
+        }
+
+        assertTrue(addrMessageCount > 0);
+
+        IoUtils.closeQuietly(fd);
+    }
+
+    /** A convenience method to create an RTM_GETADDR request message. */
+    private static byte[] newGetAddrRequest(int seqNo) {
+        final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(RtgenMsg.class);
+        final byte[] bytes = new byte[length];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_len = length;
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETADDR;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+        nlmsghdr.nlmsg_seq = seqNo;
+        nlmsghdr.pack(byteBuffer);
+
+        final RtgenMsg rtgenMsg = new RtgenMsg();
+        rtgenMsg.family = (byte) AF_UNSPEC;
+        rtgenMsg.writeToByteBuffer(byteBuffer);
+
+        return bytes;
+    }
+
+    /** From uapi/linux/rtnetlink.h */
+    private static class RtgenMsg extends Struct {
+        @Field(order = 0, type = Type.U8)
+        public short family;
+    }
+
+    /**
+     * From uapi/linux/ifaddr.h
+     *
+     * Public ensures visibility to Struct class
+     */
+    public static class IfaddrMsg extends Struct {
+        @Field(order = 0, type = Type.U8)
+        public short family;
+
+        @Field(order = 1, type = Type.U8)
+        public short prefixlen;
+
+        @Field(order = 2, type = Type.U8)
+        public short flags;
+
+        @Field(order = 3, type = Type.U8)
+        public short scope;
+
+        @Field(order = 4, type = Type.U32)
+        public long index;
+    }
 }
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
index 5b91985..6f495e7 100644
--- a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
+++ b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
@@ -1,5 +1,6 @@
 package android.net.testutils
 
+import android.annotation.SuppressLint
 import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.Network
@@ -7,6 +8,15 @@
 import com.android.testutils.ConcurrentInterpreter
 import com.android.testutils.InterpretMatcher
 import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
@@ -33,6 +43,7 @@
 const val TEST_INTERFACE_NAME = "testInterfaceName"
 
 @RunWith(JUnit4::class)
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
 class TestableNetworkCallbackTest {
     private lateinit var mCallback: TestableNetworkCallback
 
@@ -245,6 +256,41 @@
             onBlockedStatus(199)       | poll(1) = BlockedStatus(199) time 0..3
         """)
     }
+
+    @Test
+    fun testEventuallyExpect() {
+        // TODO: Current test does not verify the inline one. Also verify the behavior after
+        // aligning two eventuallyExpect()
+        val net1 = Network(100)
+        val net2 = Network(101)
+        mCallback.onAvailable(net1)
+        mCallback.onCapabilitiesChanged(net1, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net1, LinkProperties())
+        mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) {
+            net1.equals(it.network)
+        }
+        // No further new callback. Expect no callback.
+        assertFails { mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) }
+
+        // Verify no predicate set.
+        mCallback.onAvailable(net2)
+        mCallback.onLinkPropertiesChanged(net2, LinkProperties())
+        mCallback.onBlockedStatusChanged(net1, false)
+        mCallback.eventuallyExpect(BLOCKED_STATUS) { net1.equals(it.network) }
+        // Verify no callback received if the callback does not happen.
+        assertFails { mCallback.eventuallyExpect(LOSING) }
+    }
+
+    @Test
+    fun testEventuallyExpectOnMultiThreads() {
+        TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
+                threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
+                onAvailable(100)                   | eventually(CapabilitiesChanged(100), 1) fails
+                sleep ; onCapabilitiesChanged(100) | eventually(CapabilitiesChanged(100), 2)
+                onAvailable(101) ; onBlockedStatus(101) | eventually(BlockedStatus(100), 2) fails
+                onSuspended(100) ; sleep ; onLost(100)  | eventually(Lost(100), 2)
+        """)
+    }
 }
 
 private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable)
@@ -254,6 +300,7 @@
     return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name }
 }
 
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
 private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>(
     // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for
     // all callback types. This is implemented above by enumerating the subclasses of
@@ -289,5 +336,26 @@
     },
     Regex("""poll\((\d+)\)""") to { i, cb, t ->
         cb.pollForNextCallback(t.timeArg(1))
+    },
+    // Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects
+    // CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and
+    // likewise for all callback types.
+    Regex("""eventually\(($EntryList)\((\d+)\),\s+(\d+)\)""") to { i, cb, t ->
+        val net = Network(t.intArg(2))
+        val timeout = t.timeArg(3)
+        when (t.strArg(1)) {
+            "Available" -> cb.eventuallyExpect(AVAILABLE, timeout) { net == it.network }
+            "Suspended" -> cb.eventuallyExpect(SUSPENDED, timeout) { net == it.network }
+            "Resumed" -> cb.eventuallyExpect(RESUMED, timeout) { net == it.network }
+            "Losing" -> cb.eventuallyExpect(LOSING, timeout) { net == it.network }
+            "Lost" -> cb.eventuallyExpect(LOST, timeout) { net == it.network }
+            "Unavailable" -> cb.eventuallyExpect(UNAVAILABLE, timeout) { net == it.network }
+            "BlockedStatus" -> cb.eventuallyExpect(BLOCKED_STATUS, timeout) { net == it.network }
+            "CapabilitiesChanged" ->
+                cb.eventuallyExpect(NETWORK_CAPS_UPDATED, timeout) { net == it.network }
+            "LinkPropertiesChanged" ->
+                cb.eventuallyExpect(LINK_PROPERTIES_CHANGED, timeout) { net == it.network }
+            else -> fail("Unknown callback type")
+        }
     }
 )
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index d15c18f..66d1c71 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -30,7 +30,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -116,7 +118,6 @@
 import android.net.Uri;
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
-import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.SharedLog;
 import android.net.wifi.WifiInfo;
@@ -1794,24 +1795,52 @@
         runFailedNetworkTest();
     }
 
-    @Test
-    public void testNoInternetCapabilityValidated() throws Exception {
+    private void doValidationSkippedTest(NetworkCapabilities nc) throws Exception {
         // For S+, the RESULT_SKIPPED bit will be included on networks that both do not require
         // validation and for which validation is not performed.
         final int validationResult = ShimUtils.isAtLeastS()
                 ? NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_SKIPPED
                 : NETWORK_VALIDATION_RESULT_VALID;
-        runNetworkTest(TEST_LINK_PROPERTIES, CELL_NO_INTERNET_CAPABILITIES, validationResult,
+        runNetworkTest(TEST_LINK_PROPERTIES, nc, validationResult,
                 0 /* probesSucceeded */, null /* redirectUrl */);
         verify(mCleartextDnsNetwork, never()).openConnection(any());
     }
 
+    @Test
+    public void testNoInternetCapabilityValidated() throws Exception {
+        doValidationSkippedTest(CELL_NO_INTERNET_CAPABILITIES);
+    }
+
+    @Test
+    public void testNoTrustedCapabilityValidated() throws Exception {
+        final NetworkCapabilities.Builder nc = new NetworkCapabilities.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .addTransportType(TRANSPORT_CELLULAR);
+        if (ShimUtils.isAtLeastS()) {
+            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+        doValidationSkippedTest(nc.build());
+    }
+
+    @Test
+    public void testRestrictedCapabilityValidated() throws Exception {
+        final NetworkCapabilities.Builder nc = new NetworkCapabilities.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .addTransportType(TRANSPORT_CELLULAR);
+        if (ShimUtils.isAtLeastS()) {
+            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+        doValidationSkippedTest(nc.build());
+    }
+
     private NetworkCapabilities getVcnUnderlyingCarrierWifiCaps() {
         // Must be called from within the test because NOT_VCN_MANAGED is an invalid capability
         // value up to Android R. Thus, this must be guarded by an SDK check in tests that use this.
         return new NetworkCapabilities.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .removeCapability(NetworkMonitorUtils.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                 .addCapability(NET_CAPABILITY_INTERNET)