diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 74d50f8fd06298136f4707a8f45b27f80015d55d..f959114451432f17c26283d38a7f6ec9f41655c0 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -453,6 +453,7 @@ public final class NetworkCapabilities implements Parcelable {
             NET_CAPABILITY_MMTEL,
             NET_CAPABILITY_PRIORITIZE_LATENCY,
             NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
+            NET_CAPABILITY_LOCAL_NETWORK,
     })
     public @interface NetCapability { }
 
@@ -714,7 +715,21 @@ public final class NetworkCapabilities implements Parcelable {
      */
     public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
 
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+    /**
+     * This is a local network, e.g. a tethering downstream or a P2P direct network.
+     *
+     * <p>
+     * Note that local networks are not sent to callbacks by default. To receive callbacks about
+     * them, the {@link NetworkRequest} instance must be prepared to see them, either by
+     * adding the capability with {@link NetworkRequest.Builder#addCapability}, by removing
+     * this forbidden capability with {@link NetworkRequest.Builder#removeForbiddenCapability},
+     * or by clearing all capabilites with {@link NetworkRequest.Builder#clearCapabilities()}.
+     * </p>
+     * @hide
+     */
+    public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
+
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
 
     // Set all bits up to the MAX_NET_CAPABILITY-th bit
     private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
@@ -859,7 +874,7 @@ public final class NetworkCapabilities implements Parcelable {
     }
 
     /**
-     * Removes (if found) the given forbidden capability from this {@code NetworkCapability}
+     * Removes (if found) the given forbidden capability from this {@link NetworkCapabilities}
      * instance that were added via addForbiddenCapability(int) or setCapabilities(int[], int[]).
      *
      * @param capability the capability to be removed.
@@ -872,6 +887,16 @@ public final class NetworkCapabilities implements Parcelable {
         return this;
     }
 
+    /**
+     * Removes all forbidden capabilities from this {@link NetworkCapabilities} instance.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
+     * @hide
+     */
+    public @NonNull NetworkCapabilities removeAllForbiddenCapabilities() {
+        mForbiddenNetworkCapabilities = 0;
+        return this;
+    }
+
     /**
      * Sets (or clears) the given capability on this {@link NetworkCapabilities}
      * instance.
@@ -1039,11 +1064,12 @@ public final class NetworkCapabilities implements Parcelable {
     }
 
     /**
-     * Check if this NetworkCapabilities has system managed capabilities or not.
+     * Check if this NetworkCapabilities has connectivity-managed capabilities or not.
      * @hide
      */
     public boolean hasConnectivityManagedCapability() {
-        return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
+        return (mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0
+                || mForbiddenNetworkCapabilities != 0;
     }
 
     /**
@@ -2523,6 +2549,7 @@ public final class NetworkCapabilities implements Parcelable {
             case NET_CAPABILITY_MMTEL:                return "MMTEL";
             case NET_CAPABILITY_PRIORITIZE_LATENCY:          return "PRIORITIZE_LATENCY";
             case NET_CAPABILITY_PRIORITIZE_BANDWIDTH:        return "PRIORITIZE_BANDWIDTH";
+            case NET_CAPABILITY_LOCAL_NETWORK:        return "LOCAL_NETWORK";
             default:                                  return Integer.toString(capability);
         }
     }
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 5cc9f1b605dac9d6e7edffc10d6cb116b5810c8a..9824faa1b514d813e1ddca46053b46173aaab966 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,6 +20,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -283,6 +284,15 @@ public class NetworkRequest implements Parcelable {
                 NET_CAPABILITY_TRUSTED,
                 NET_CAPABILITY_VALIDATED);
 
+        /**
+         * Capabilities that are forbidden by default.
+         * Forbidden capabilities only make sense in NetworkRequest, not for network agents.
+         * Therefore these capabilities are only in NetworkRequest.
+         */
+        private static final int[] DEFAULT_FORBIDDEN_CAPABILITIES = new int[] {
+            NET_CAPABILITY_LOCAL_NETWORK
+        };
+
         private final NetworkCapabilities mNetworkCapabilities;
 
         // A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when
@@ -298,6 +308,16 @@ public class NetworkRequest implements Parcelable {
             // it for apps that do not have the NETWORK_SETTINGS permission.
             mNetworkCapabilities = new NetworkCapabilities();
             mNetworkCapabilities.setSingleUid(Process.myUid());
+            // Default forbidden capabilities are foremost meant to help with backward
+            // compatibility. When adding new types of network identified by a capability that
+            // might confuse older apps, a default forbidden capability will have apps not see
+            // these networks unless they explicitly ask for it.
+            // If the app called clearCapabilities() it will see everything, but then it
+            // can be argued that it's fair to send them too, since it asked for everything
+            // explicitly.
+            for (final int forbiddenCap : DEFAULT_FORBIDDEN_CAPABILITIES) {
+                mNetworkCapabilities.addForbiddenCapability(forbiddenCap);
+            }
         }
 
         /**
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 2522958310dc888456eb51044c63c1bbd226819d..00382f602b1f84ffa27eab729727e7cb83e89acd 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -45,7 +45,8 @@ public final class NetworkScore implements Parcelable {
     @IntDef(value = {
             KEEP_CONNECTED_NONE,
             KEEP_CONNECTED_FOR_HANDOVER,
-            KEEP_CONNECTED_FOR_TEST
+            KEEP_CONNECTED_FOR_TEST,
+            KEEP_CONNECTED_DOWNSTREAM_NETWORK
     })
     public @interface KeepConnectedReason { }
 
@@ -64,6 +65,12 @@ public final class NetworkScore implements Parcelable {
      * @hide
      */
     public static final int KEEP_CONNECTED_FOR_TEST = 2;
+    /**
+     * Keep this network connected even if there is no outstanding request for it, because
+     * it is a downstream network.
+     * @hide
+     */
+    public static final int KEEP_CONNECTED_DOWNSTREAM_NETWORK = 3;
 
     // Agent-managed policies
     // This network should lose to a wifi that has ever been validated
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index bdd841f9ed6ba0fe36589c349ce04b141a2dcb42..0f72cd462d54b387d5b6df1c08710587e166ca5b 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -453,6 +453,8 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
      * apply to the allowedUids field.
      * They also should not mutate immutable capabilities, although for backward-compatibility
      * this is not enforced and limited to just a log.
+     * Forbidden capabilities also make no sense for networks, so they are disallowed and
+     * will be ignored with a warning.
      *
      * @param carrierPrivilegeAuthenticator the authenticator, to check access UIDs.
      */
@@ -461,6 +463,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
         final NetworkCapabilities nc = new NetworkCapabilities(mDeclaredCapabilitiesUnsanitized);
         if (nc.hasConnectivityManagedCapability()) {
             Log.wtf(TAG, "BUG: " + this + " has CS-managed capability.");
+            nc.removeAllForbiddenCapabilities();
         }
         if (networkCapabilities.getOwnerUid() != nc.getOwnerUid()) {
             Log.e(TAG, toShortString() + ": ignoring attempt to change owner from "
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
index 35f22b9e587d45771b05382d7a3ceeeb2050d7e6..46229b0cd2de58b003f440b835cdfaa6a7ab6de6 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -27,6 +27,9 @@ import org.junit.runners.model.Statement
 
 @Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
 const val SC_V2 = Build.VERSION_CODES.S_V2
+// TODO: Remove this when Build.VERSION_CODES.VANILLA_ICE_CREAM is available in all branches
+// where this code builds
+const val VANILLA_ICE_CREAM = 35 // Bui1ld.VERSION_CODES.VANILLA_ICE_CREAM
 
 private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
 private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index aae342574f81061e033dc309eed93f4b8fe00eb4..bec9a4a61646aa848b28762ad06b1449e674001a 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -26,6 +26,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -63,6 +64,7 @@ import static android.os.Process.INVALID_UID;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertEmpty;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -369,6 +371,9 @@ public class NetworkCapabilitiesTest {
             .addCapability(NET_CAPABILITY_INTERNET)
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
+        if (isAtLeastV()) {
+            netCap.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+        }
         if (isAtLeastS()) {
             final ArraySet<Integer> allowedUids = new ArraySet<>();
             allowedUids.add(4);
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 718ca6dea48f8e82fb01337cd46075e8769bf58d..594f3fbaab8f2071a8d68bf350eaa1114aab0a21 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -19,8 +19,10 @@ package android.net.cts;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
@@ -28,6 +30,8 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
+
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -489,6 +493,32 @@ public class NetworkRequestTest {
         assertArrayEquals(netCapabilities, nr.getCapabilities());
     }
 
+    @Test @IgnoreUpTo(VANILLA_ICE_CREAM)
+    public void testDefaultCapabilities() {
+        final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
+        assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
+        assertFalse(defaultNR.hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+        assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VPN));
+
+        final NetworkCapabilities emptyNC =
+                NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
+        assertFalse(defaultNR.canBeSatisfiedBy(emptyNC));
+
+        // defaultNC represent the capabilities of a network agent, so they must not contain
+        // forbidden capabilities by default.
+        final NetworkCapabilities defaultNC = new NetworkCapabilities.Builder().build();
+        assertArrayEquals(new int[0], defaultNC.getForbiddenCapabilities());
+        // A default NR can be satisfied by default NC.
+        assertTrue(defaultNR.canBeSatisfiedBy(defaultNC));
+
+        // Conversely, network requests have forbidden capabilities by default to manage
+        // backward compatibility, so test that these forbidden capabilities are in place.
+        // Starting in V, NET_CAPABILITY_LOCAL_NETWORK is introduced but is not seen by
+        // default, thanks to a default forbidden capability in NetworkRequest.
+        defaultNC.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+        assertFalse(defaultNR.canBeSatisfiedBy(defaultNC));
+    }
+
     @Test
     public void testBuildRequestFromExistingRequestWithBuilder() {
         assumeTrue(TestUtils.shouldTestSApis());
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
index a9b9896a0944adfec22e7b0d14420018845f8d16..86426c20660505618bf02fd3579e9dada5b94201 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -16,8 +16,12 @@
 
 package com.android.server
 
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkRequest
 import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_DOWNSTREAM_NETWORK
 import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
 import android.os.Build
 import androidx.test.filters.SmallTest
@@ -32,6 +36,20 @@ import org.junit.runner.RunWith
 @SmallTest
 @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
 class CSKeepConnectedTest : CSTest() {
+    @Test
+    fun testKeepConnectedLocalAgent() {
+        deps.setBuildSdk(VERSION_V)
+        val nc = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+                .build()
+        val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
+                .setKeepConnectedReason(KEEP_CONNECTED_DOWNSTREAM_NETWORK)
+                .build()))
+        val dontKeepConnectedAgent = Agent(nc = nc)
+        doTestKeepConnected(keepConnectedAgent, dontKeepConnectedAgent)
+    }
+
     @Test
     fun testKeepConnectedForTest() {
         val keepAgent = Agent(score = FromS(NetworkScore.Builder()