Merge "Mark testFirewallBlocking as ConnectivityModuleTest"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4774866..1e8babf 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -36,6 +36,17 @@
     },
     // CTS tests that target older SDKs.
     {
+      "name": "CtsNetTestCasesMaxTargetSdk30",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
       "name": "CtsNetTestCasesMaxTargetSdk31",
       "options": [
         {
@@ -103,6 +114,17 @@
       ]
     },
     {
+      "name": "CtsNetTestCasesMaxTargetSdk30[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
       "name": "CtsNetTestCasesMaxTargetSdk31[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
       "options": [
         {
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index a7028b7..60b4e91 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -108,7 +108,10 @@
 
     androidManifest: "AndroidManifest.xml",
 
-    compat_configs: ["connectivity-platform-compat-config"],
+    compat_configs: [
+        "connectivity-platform-compat-config",
+        "connectivity-t-platform-compat-config",
+    ],
 }
 
 apex_key {
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index b4e3ba4..836761f 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -36,4 +36,5 @@
     void onTetherStatesChanged(in TetherStatesParcel states);
     void onTetherClientsChanged(in List<TetheredClient> clients);
     void onOffloadStatusChanged(int status);
+    void onSupportedTetheringTypes(long supportedBitmap);
 }
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index 253eacb..f33f846 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -26,7 +26,7 @@
  * @hide
  */
 parcelable TetheringCallbackStartedParcel {
-    boolean tetheringSupported;
+    long supportedTypes;
     Network upstreamNetwork;
     TetheringConfigurationParcel config;
     TetherStatesParcel states;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 6f9b33e..b3f0cf2 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -183,6 +183,12 @@
      */
     public static final int TETHERING_WIGIG = 6;
 
+    /**
+     * The int value of last tethering type.
+     * @hide
+     */
+    public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
@@ -520,6 +526,9 @@
         }
 
         @Override
+        public void onSupportedTetheringTypes(long supportedBitmap) { }
+
+        @Override
         public void onUpstreamChanged(Network network) { }
 
         @Override
@@ -1033,15 +1042,29 @@
         /**
          * Called when tethering supported status changed.
          *
+         * <p>This callback will be called immediately after the callback is
+         * registered, and never be called if there is changes afterward.
+         *
+         * <p>Tethering may be disabled via system properties, device configuration, or device
+         * policy restrictions.
+         *
+         * @param supported whether any tethering type is supported.
+         */
+        default void onTetheringSupported(boolean supported) {}
+
+        /**
+         * Called when tethering supported status changed.
+         *
          * <p>This will be called immediately after the callback is registered, and may be called
          * multiple times later upon changes.
          *
          * <p>Tethering may be disabled via system properties, device configuration, or device
          * policy restrictions.
          *
-         * @param supported The new supported status
+         * @param supportedTypes a set of @TetheringType which is supported.
+         * @hide
          */
-        default void onTetheringSupported(boolean supported) {}
+        default void onSupportedTetheringTypes(@NonNull Set<Integer> supportedTypes) {}
 
         /**
          * Called when tethering upstream changed.
@@ -1339,7 +1362,8 @@
                 @Override
                 public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
                     executor.execute(() -> {
-                        callback.onTetheringSupported(parcel.tetheringSupported);
+                        callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes));
+                        callback.onTetheringSupported(parcel.supportedTypes != 0);
                         callback.onUpstreamChanged(parcel.upstreamNetwork);
                         sendErrorCallbacks(parcel.states);
                         sendRegexpsChanged(parcel.config);
@@ -1358,6 +1382,13 @@
                     });
                 }
 
+                @Override
+                public void onSupportedTetheringTypes(long supportedBitmap) {
+                    executor.execute(() -> {
+                        callback.onSupportedTetheringTypes(unpackBits(supportedBitmap));
+                    });
+                }
+
                 private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
                     callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
                             parcel.tetherableBluetoothRegexs,
@@ -1396,6 +1427,23 @@
     }
 
     /**
+     * Unpack bitmap to a set of bit position intergers.
+     * @hide
+     */
+    public static ArraySet<Integer> unpackBits(long val) {
+        final ArraySet<Integer> result = new ArraySet<>(Long.bitCount(val));
+        int bitPos = 0;
+        while (val != 0) {
+            if ((val & 1) == 1) result.add(bitPos);
+
+            val = val >>> 1;
+            bitPos++;
+        }
+
+        return result;
+    }
+
+    /**
      * Remove tethering event callback previously registered with
      * {@link #registerTetheringEventCallback}.
      *
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 89ed620..0c59b61 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -281,6 +281,11 @@
     private BluetoothPan mBluetoothPan;
     private PanServiceListener mBluetoothPanListener;
     private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
+    // AIDL doesn't support Set<Integer>. Maintain a int bitmap here. When the bitmap is passed to
+    // TetheringManager, TetheringManager would convert it to a set of Integer types.
+    // mSupportedTypeBitmap should always be updated inside tethering internal thread but it may be
+    // read from binder thread which called TetheringService directly.
+    private volatile long mSupportedTypeBitmap;
 
     public Tethering(TetheringDependencies deps) {
         mLog.mark("Tethering.constructed");
@@ -512,6 +517,8 @@
         mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically,
                 mConfig.isDunRequired);
         reportConfigurationChanged(mConfig.toStableParcelable());
+
+        updateSupportedDownstreams(mConfig);
     }
 
     private void maybeDunSettingChanged() {
@@ -1550,26 +1557,6 @@
         return mConfig;
     }
 
-    boolean hasAnySupportedDownstream() {
-        if ((mConfig.tetherableUsbRegexs.length != 0)
-                || (mConfig.tetherableWifiRegexs.length != 0)
-                || (mConfig.tetherableBluetoothRegexs.length != 0)) {
-            return true;
-        }
-
-        // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
-        // disabled (whole tethering settings would be hidden). This means tethering would also not
-        // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
-        // some devices in the field rely on this to disable tethering entirely.
-        if (!SdkLevel.isAtLeastT()) return false;
-
-        return (mConfig.tetherableWifiP2pRegexs.length != 0)
-                || (mConfig.tetherableNcmRegexs.length != 0)
-                || isEthernetSupported();
-    }
-
-    // TODO: using EtherentManager new API to check whether ethernet is supported when the API is
-    // ready to use.
     private boolean isEthernetSupported() {
         return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
     }
@@ -2359,7 +2346,7 @@
         mHandler.post(() -> {
             mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
             final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
-            parcel.tetheringSupported = isTetheringSupported();
+            parcel.supportedTypes = mSupportedTypeBitmap;
             parcel.upstreamNetwork = mTetherUpstream;
             parcel.config = mConfig.toStableParcelable();
             parcel.states =
@@ -2398,6 +2385,22 @@
         });
     }
 
+    private void reportTetheringSupportedChange(final long supportedBitmap) {
+        final int length = mTetheringEventCallbacks.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    mTetheringEventCallbacks.getBroadcastItem(i).onSupportedTetheringTypes(
+                            supportedBitmap);
+                } catch (RemoteException e) {
+                    // Not really very much to do here.
+                }
+            }
+        } finally {
+            mTetheringEventCallbacks.finishBroadcast();
+        }
+    }
+
     private void reportUpstreamChanged(UpstreamNetworkState ns) {
         final int length = mTetheringEventCallbacks.beginBroadcast();
         final Network network = (ns != null) ? ns.network : null;
@@ -2482,18 +2485,56 @@
         }
     }
 
+    private void updateSupportedDownstreams(final TetheringConfiguration config) {
+        final long preSupportedBitmap = mSupportedTypeBitmap;
+
+        if (!isTetheringAllowed() || mEntitlementMgr.isProvisioningNeededButUnavailable()) {
+            mSupportedTypeBitmap = 0;
+        } else {
+            mSupportedTypeBitmap = makeSupportedDownstreams(config);
+        }
+
+        if (preSupportedBitmap != mSupportedTypeBitmap) {
+            reportTetheringSupportedChange(mSupportedTypeBitmap);
+        }
+    }
+
+    private long makeSupportedDownstreams(final TetheringConfiguration config) {
+        long types = 0;
+        if (config.tetherableUsbRegexs.length != 0) types |= (1 << TETHERING_USB);
+
+        if (config.tetherableWifiRegexs.length != 0) types |= (1 << TETHERING_WIFI);
+
+        if (config.tetherableBluetoothRegexs.length != 0) types |= (1 << TETHERING_BLUETOOTH);
+
+        // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
+        // disabled (whole tethering settings would be hidden). This means tethering would also not
+        // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
+        // some devices in the field rely on this to disable tethering entirely.
+        if (!SdkLevel.isAtLeastT() && types == 0) return types;
+
+        if (config.tetherableNcmRegexs.length != 0) types |= (1 << TETHERING_NCM);
+
+        if (config.tetherableWifiP2pRegexs.length != 0) types |= (1 << TETHERING_WIFI_P2P);
+
+        if (isEthernetSupported()) types |= (1 << TETHERING_ETHERNET);
+
+        return types;
+    }
+
     // if ro.tether.denied = true we default to no tethering
     // gservices could set the secure setting to 1 though to enable it on a build where it
     // had previously been turned off.
-    boolean isTetheringSupported() {
+    boolean isTetheringAllowed() {
         final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1;
         final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
-        final boolean tetherEnabledInSettings = tetherSupported
+        return tetherSupported
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+    }
 
-        return tetherEnabledInSettings && hasAnySupportedDownstream()
-                && !mEntitlementMgr.isProvisioningNeededButUnavailable();
+    boolean isTetheringSupported() {
+        return mSupportedTypeBitmap > 0;
     }
 
     private void dumpBpf(IndentingPrintWriter pw) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index f147e10..96ddfa0 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -237,7 +237,7 @@
                     listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
                     return true;
                 }
-                if (!mTethering.isTetheringSupported()) {
+                if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) {
                     listener.onResult(TETHER_ERROR_UNSUPPORTED);
                     return true;
                 }
@@ -255,7 +255,7 @@
                 receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
                 return true;
             }
-            if (!mTethering.isTetheringSupported()) {
+            if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) {
                 receiver.send(TETHER_ERROR_UNSUPPORTED, null);
                 return true;
             }
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 86dca1c..a2385c8 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -144,6 +144,7 @@
     private static final int TX_UDP_PACKET_COUNT = 123;
     private static final long WAIT_RA_TIMEOUT_MS = 2000;
 
+    private static final MacAddress TEST_MAC = MacAddress.fromString("1:2:3:4:5:6");
     private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
     private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
     private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
@@ -151,6 +152,8 @@
     private static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96");
     private static final Inet6Address REMOTE_NAT64_ADDR =
             (Inet6Address) parseNumericAddress("64:ff9b::808:808");
+    private static final Inet6Address REMOTE_IP6_ADDR =
+            (Inet6Address) parseNumericAddress("2002:db8:1::515:ca");
     private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
             ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
 
@@ -192,12 +195,13 @@
         mUiAutomation.adoptShellPermissionIdentity(
                 MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
                 CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
-        mRunTests = mTm.isTetheringSupported() && mEm != null;
-        assumeTrue(mRunTests);
-
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
+
+        mRunTests = isEthernetTetheringSupported();
+        assumeTrue(mRunTests);
+
         mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
     }
 
@@ -225,7 +229,6 @@
             mHandler.post(() -> reader.stop());
             mDownstreamReader = null;
         }
-        mHandlerThread.quitSafely();
         mTetheredInterfaceRequester.release();
         mEm.setIncludeTestInterfaces(false);
         maybeDeleteTestInterface();
@@ -236,6 +239,7 @@
         try {
             if (mRunTests) cleanUp();
         } finally {
+            mHandlerThread.quitSafely();
             mUiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -410,6 +414,23 @@
         // client, which is not possible in this test.
     }
 
+    private boolean isEthernetTetheringSupported() throws Exception {
+        final CompletableFuture<Boolean> future = new CompletableFuture<>();
+        final TetheringEventCallback callback = new TetheringEventCallback() {
+            @Override
+            public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
+                future.complete(supportedTypes.contains(TETHERING_ETHERNET));
+            }
+        };
+
+        try {
+            mTm.registerTetheringEventCallback(mHandler::post, callback);
+            return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } finally {
+            mTm.unregisterTetheringEventCallback(callback);
+        }
+    }
+
     private static final class MyTetheringEventCallback implements TetheringEventCallback {
         private final TetheringManager mTm;
         private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
@@ -852,7 +873,7 @@
         final PacketBuilder packetBuilder = new PacketBuilder(buffer);
 
         // [1] Ethernet header
-        if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
+        if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ethType);
 
         // [2] IP header
         if (ipProto == IPPROTO_IP) {
@@ -885,6 +906,54 @@
                 dstPort, payload);
     }
 
+    private boolean isAddressIpv4(@NonNull final  InetAddress srcIp,
+            @NonNull final InetAddress dstIp) {
+        if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) return true;
+        if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) return false;
+
+        fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
+        return false;  // unreachable
+    }
+
+    private void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
+            @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester)
+            throws Exception {
+        final ByteBuffer testPacket = buildUdpPacket(null /* srcMac */, null /* dstMac */,
+                srcIp, dstIp, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, PAYLOAD);
+
+        tester.verifyDownload(testPacket, p -> {
+            Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+            return isExpectedUdpPacket(p, true /* hasEther */, isAddressIpv4(srcIp, dstIp),
+                PAYLOAD);
+        });
+    }
+
+    private void sendUploadPacketUdp(@NonNull final MacAddress srcMac,
+            @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
+            @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester)
+            throws Exception {
+        final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
+                LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, PAYLOAD2);
+
+        tester.verifyUpload(testPacket, p -> {
+            Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+            return isExpectedUdpPacket(p, false /* hasEther */, isAddressIpv4(srcIp, dstIp),
+                PAYLOAD2);
+        });
+    }
+
+    @Test
+    public void testTetherUdpV6() throws Exception {
+        final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+                toList(TEST_IP6_DNS));
+        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+        sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr,
+                tethered.ipv6Addr, REMOTE_IP6_ADDR, tester);
+        sendDownloadPacketUdp(REMOTE_IP6_ADDR, tethered.ipv6Addr, tester);
+
+        // TODO: test BPF offload maps {rule, stats}.
+    }
+
     // TODO: remove ipv4 verification (is4To6 = false) once upstream connected notification race is
     // fixed. See #runUdp4Test.
     //
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 9db8f16..e114cb5 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -24,6 +24,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -139,23 +140,27 @@
     }
 
     private void runAsNoPermission(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, new String[0]);
+        runTetheringCall(test, true /* isTetheringAllowed */, new String[0]);
     }
 
     private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, TETHER_PRIVILEGED);
+        runTetheringCall(test, true /* isTetheringAllowed */, TETHER_PRIVILEGED);
     }
 
     private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, ACCESS_NETWORK_STATE);
+        runTetheringCall(test, true /* isTetheringAllowed */, ACCESS_NETWORK_STATE);
     }
 
     private void runAsWriteSettings(final TestTetheringCall test) throws Exception {
-        runTetheringCall(test, WRITE_SETTINGS);
+        runTetheringCall(test, true /* isTetheringAllowed */, WRITE_SETTINGS);
     }
 
-    private void runTetheringCall(final TestTetheringCall test, String... permissions)
-            throws Exception {
+    private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception {
+        runTetheringCall(test, false /* isTetheringAllowed */, TETHER_PRIVILEGED);
+    }
+
+    private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
+            String... permissions) throws Exception {
         // Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
         if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) {
             mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED);
@@ -164,6 +169,7 @@
         if (permissions.length > 0) mUiAutomation.adoptShellPermissionIdentity(permissions);
         try {
             when(mTethering.isTetheringSupported()).thenReturn(true);
+            when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed);
             test.runTetheringCall(new TestTetheringResult());
         } finally {
             mUiAutomation.dropShellPermissionIdentity();
@@ -180,6 +186,7 @@
     private void runTether(final TestTetheringResult result) throws Exception {
         mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).tether(TEST_IFACE_NAME, IpServer.STATE_TETHERED, result);
     }
 
@@ -203,12 +210,22 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runUnTether(final TestTetheringResult result) throws Exception {
         mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                 result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).untether(eq(TEST_IFACE_NAME), eq(result));
     }
 
@@ -232,6 +249,15 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runSetUsbTethering(final TestTetheringResult result) throws Exception {
@@ -243,6 +269,7 @@
         mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
                 TEST_ATTRIBUTION_TAG, result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).setUsbTethering(eq(true) /* enable */, any(IIntResultListener.class));
         result.assertResult(TETHER_ERROR_NO_ERROR);
     }
@@ -268,6 +295,14 @@
             verifyNoMoreInteractionsForTethering();
         });
 
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
+                    TEST_ATTRIBUTION_TAG, result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runStartTethering(final TestTetheringResult result,
@@ -275,6 +310,7 @@
         mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
                 result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
     }
 
@@ -301,6 +337,15 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runStartTetheringAndVerifyNoPermission(final TestTetheringResult result)
@@ -337,6 +382,7 @@
         mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
                 TEST_ATTRIBUTION_TAG, result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).stopTethering(TETHERING_WIFI);
         result.assertResult(TETHER_ERROR_NO_ERROR);
     }
@@ -361,6 +407,15 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
+                    TEST_ATTRIBUTION_TAG, result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runRequestLatestTetheringEntitlementResult() throws Exception {
@@ -368,6 +423,7 @@
         mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
                 true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
                 eq(result), eq(true) /* showEntitlementUi */);
     }
@@ -392,6 +448,16 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((none) -> {
+            final MyResultReceiver receiver = new MyResultReceiver(null);
+            mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver,
+                    true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            receiver.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runRegisterTetheringEventCallback() throws Exception {
@@ -419,6 +485,12 @@
             runRegisterTetheringEventCallback();
             verifyNoMoreInteractionsForTethering();
         });
+
+        // should still be able to register callback even tethering is restricted.
+        runAsTetheringDisallowed((result) -> {
+            runRegisterTetheringEventCallback();
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runUnregisterTetheringEventCallback() throws Exception {
@@ -446,11 +518,19 @@
             runUnregisterTetheringEventCallback();
             verifyNoMoreInteractionsForTethering();
         });
+
+        // should still be able to unregister callback even tethering is restricted.
+        runAsTetheringDisallowed((result) -> {
+            runUnregisterTetheringEventCallback();
+            verifyNoMoreInteractionsForTethering();
+        });
+
     }
 
     private void runStopAllTethering(final TestTetheringResult result) throws Exception {
         mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         verify(mTethering).untetherAll();
         result.assertResult(TETHER_ERROR_NO_ERROR);
     }
@@ -474,11 +554,20 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private void runIsTetheringSupported(final TestTetheringResult result) throws Exception {
         mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
         verify(mTethering).isTetheringSupported();
+        verify(mTethering).isTetheringAllowed();
         result.assertResult(TETHER_ERROR_NO_ERROR);
     }
 
@@ -502,6 +591,15 @@
             verify(mTethering).isTetherProvisioningRequired();
             verifyNoMoreInteractionsForTethering();
         });
+
+        runAsTetheringDisallowed((result) -> {
+            mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+                    result);
+            verify(mTethering).isTetheringSupported();
+            verify(mTethering).isTetheringAllowed();
+            result.assertResult(TETHER_ERROR_UNSUPPORTED);
+            verifyNoMoreInteractionsForTethering();
+        });
     }
 
     private class ConnectorSupplier<T> implements Supplier<T> {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index b402bc3..8de7836 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -144,6 +144,7 @@
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetheringInterface;
+import android.net.TetheringManager;
 import android.net.TetheringRequestParcel;
 import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
@@ -175,6 +176,7 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
+import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -217,6 +219,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.Vector;
 
 @RunWith(AndroidJUnit4.class)
@@ -1729,6 +1732,7 @@
         private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
         private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
         private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
+        private final ArrayList<Long> mSupportedBitmaps = new ArrayList<>();
 
         // This function will remove the recorded callbacks, so it must be called once for
         // each callback. If this is called after multiple callback, the order matters.
@@ -1781,6 +1785,10 @@
             assertTrue(leases.containsAll(result));
         }
 
+        public void expectSupportedTetheringTypes(Set<Integer> expectedTypes) {
+            assertEquals(expectedTypes, TetheringManager.unpackBits(mSupportedBitmaps.remove(0)));
+        }
+
         @Override
         public void onUpstreamChanged(Network network) {
             mActualUpstreams.add(network);
@@ -1813,11 +1821,17 @@
             mTetherStates.add(parcel.states);
             mOffloadStatus.add(parcel.offloadStatus);
             mTetheredClients.add(parcel.tetheredClients);
+            mSupportedBitmaps.add(parcel.supportedTypes);
         }
 
         @Override
         public void onCallbackStopped(int errorCode) { }
 
+        @Override
+        public void onSupportedTetheringTypes(long supportedBitmap) {
+            mSupportedBitmaps.add(supportedBitmap);
+        }
+
         public void assertNoUpstreamChangeCallback() {
             assertTrue(mActualUpstreams.isEmpty());
         }
@@ -2945,53 +2959,81 @@
         runStopUSBTethering();
     }
 
+    public static ArraySet<Integer> getAllSupportedTetheringTypes() {
+        return new ArraySet<>(new Integer[] { TETHERING_USB, TETHERING_NCM, TETHERING_WIFI,
+                TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
+    }
+
     @Test
     public void testTetheringSupported() throws Exception {
+        final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
+        // Check tethering is supported after initialization.
         setTetheringSupported(true /* supported */);
-        updateConfigAndVerifySupported(true /* supported */);
+        TestTetheringEventCallback callback = new TestTetheringEventCallback();
+        mTethering.registerTetheringEventCallback(callback);
+        mLooper.dispatchAll();
+        updateConfigAndVerifySupported(callback, expectedTypes);
 
         // Could disable tethering supported by settings.
         Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
-        updateConfigAndVerifySupported(false /* supported */);
+        updateConfigAndVerifySupported(callback, new ArraySet<>());
 
         // Could disable tethering supported by user restriction.
         setTetheringSupported(true /* supported */);
+        updateConfigAndVerifySupported(callback, expectedTypes);
         when(mUserManager.hasUserRestriction(
                 UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true);
-        updateConfigAndVerifySupported(false /* supported */);
+        updateConfigAndVerifySupported(callback, new ArraySet<>());
 
         // Tethering is supported if it has any supported downstream.
         setTetheringSupported(true /* supported */);
+        updateConfigAndVerifySupported(callback, expectedTypes);
+        // Usb tethering is not supported:
+        expectedTypes.remove(TETHERING_USB);
         when(mResources.getStringArray(R.array.config_tether_usb_regexs))
                 .thenReturn(new String[0]);
-        updateConfigAndVerifySupported(true /* supported */);
+        updateConfigAndVerifySupported(callback, expectedTypes);
+        // Wifi tethering is not supported:
+        expectedTypes.remove(TETHERING_WIFI);
         when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[0]);
-        updateConfigAndVerifySupported(true /* supported */);
-
+        updateConfigAndVerifySupported(callback, expectedTypes);
+        // Bluetooth tethering is not supported:
+        expectedTypes.remove(TETHERING_BLUETOOTH);
+        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+                .thenReturn(new String[0]);
 
         if (isAtLeastT()) {
-            when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
-                    .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(true /* supported */);
+            updateConfigAndVerifySupported(callback, expectedTypes);
+
+            // P2p tethering is not supported:
+            expectedTypes.remove(TETHERING_WIFI_P2P);
             when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                     .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(true /* supported */);
+            updateConfigAndVerifySupported(callback, expectedTypes);
+            // Ncm tethering is not supported:
+            expectedTypes.remove(TETHERING_NCM);
             when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
                     .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(true /* supported */);
+            updateConfigAndVerifySupported(callback, expectedTypes);
+            // Ethernet tethering (last supported type) is not supported:
+            expectedTypes.remove(TETHERING_ETHERNET);
             mForceEthernetServiceUnavailable = true;
-            updateConfigAndVerifySupported(false /* supported */);
+            updateConfigAndVerifySupported(callback, new ArraySet<>());
+
         } else {
-            when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
-                    .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(false /* supported */);
+            // If wifi, usb and bluetooth are all not supported, all the types are not supported.
+            expectedTypes.clear();
+            updateConfigAndVerifySupported(callback, expectedTypes);
         }
     }
 
-    private void updateConfigAndVerifySupported(boolean supported) {
+    private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
+            final ArraySet<Integer> expectedTypes) {
         sendConfigurationChanged();
-        assertEquals(supported, mTethering.isTetheringSupported());
+
+        assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
+        callback.expectSupportedTetheringTypes(expectedTypes);
     }
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index c2e28f4..2f4cbcd 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -50,7 +50,7 @@
         "//packages/modules/Connectivity/service/native/libs/libclat",
         "//packages/modules/Connectivity/Tethering",
         "//packages/modules/Connectivity/service/native",
-        "//packages/modules/Connectivity/tests/native",
+        "//packages/modules/Connectivity/tests/native/connectivity_native_test",
         "//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
         "//packages/modules/Connectivity/tests/unit/jni",
         "//system/netd/tests",
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index f2116f2..f308931 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -34,19 +34,10 @@
 #include "dscpPolicy.h"
 
 #define ECN_MASK 3
-#define IP4_OFFSET(field, header) (header + offsetof(struct iphdr, field))
-#define UPDATE_TOS(dscp, tos) (dscp << 2) | (tos & ECN_MASK)
+#define IP4_OFFSET(field, header) ((header) + offsetof(struct iphdr, field))
+#define UPDATE_TOS(dscp, tos) ((dscp) << 2) | ((tos) & ECN_MASK)
 
-DEFINE_BPF_MAP_GRW(switch_comp_map, ARRAY, int, uint64_t, 1, AID_SYSTEM)
-
-DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-                   AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-                   AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-                   AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-                   AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(socket_policy_cache_map, HASH, uint64_t, RuleEntry, CACHE_MAP_SIZE, AID_SYSTEM)
 
 DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES, AID_SYSTEM)
 DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES, AID_SYSTEM)
@@ -60,14 +51,7 @@
 
     if (data + l2_header_size > data_end) return;
 
-    int zero = 0;
     int hdr_size = 0;
-    uint64_t* selected_map = bpf_switch_comp_map_lookup_elem(&zero);
-
-    // use this with HASH map so map lookup only happens once policies have been added?
-    if (!selected_map) {
-        return;
-    }
 
     // used for map lookup
     uint64_t cookie = bpf_get_socket_cookie(skb);
@@ -135,25 +119,13 @@
             return;
     }
 
-    RuleEntry* existing_rule;
-    if (ipv4) {
-        if (*selected_map == MAP_A) {
-            existing_rule = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
-        } else {
-            existing_rule = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
-        }
-    } else {
-        if (*selected_map == MAP_A) {
-            existing_rule = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
-        } else {
-            existing_rule = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
-        }
-    }
+    RuleEntry* existing_rule = bpf_socket_policy_cache_map_lookup_elem(&cookie);
 
     if (existing_rule && v6_equal(src_ip, existing_rule->src_ip) &&
             v6_equal(dst_ip, existing_rule->dst_ip) && skb->ifindex == existing_rule->ifindex &&
         ntohs(sport) == htons(existing_rule->src_port) &&
         ntohs(dport) == htons(existing_rule->dst_port) && protocol == existing_rule->proto) {
+        if (existing_rule->dscp_val < 0) return;
         if (ipv4) {
             uint8_t newTos = UPDATE_TOS(existing_rule->dscp_val, tos);
             bpf_l3_csum_replace(skb, IP4_OFFSET(check, l2_header_size), htons(tos), htons(newTos),
@@ -169,8 +141,8 @@
     }
 
     // Linear scan ipv4_dscp_policies_map since no stored params match skb.
-    int best_score = -1;
-    uint32_t best_match = 0;
+    int best_score = 0;
+    int8_t new_dscp = -1;
 
     for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
         int score = 0;
@@ -218,25 +190,10 @@
         }
 
         if (score > best_score && temp_mask == policy->present_fields) {
-            best_match = i;
             best_score = score;
-        }
-    }
-
-    uint8_t new_dscp = 0;
-    if (best_score > 0) {
-        DscpPolicy* policy;
-        if (ipv4) {
-            policy = bpf_ipv4_dscp_policies_map_lookup_elem(&best_match);
-        } else {
-            policy = bpf_ipv6_dscp_policies_map_lookup_elem(&best_match);
-        }
-
-        if (policy) {
             new_dscp = policy->dscp_val;
         }
-    } else
-        return;
+    }
 
     RuleEntry value = {
         .src_ip = src_ip,
@@ -248,20 +205,10 @@
         .dscp_val = new_dscp,
     };
 
-    // Update map with new policy.
-    if (ipv4) {
-        if (*selected_map == MAP_A) {
-            bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
-        } else {
-            bpf_ipv4_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
-        }
-    } else {
-        if (*selected_map == MAP_A) {
-            bpf_ipv6_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
-        } else {
-            bpf_ipv6_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
-        }
-    }
+    // Update cache with found policy.
+    bpf_socket_policy_cache_map_update_elem(&cookie, &value, BPF_ANY);
+
+    if (new_dscp < 0) return;
 
     // Need to store bytes after updating map or program will not load.
     if (ipv4) {
diff --git a/bpf_progs/dscpPolicy.h b/bpf_progs/dscpPolicy.h
index 455a121..c1db6ab 100644
--- a/bpf_progs/dscpPolicy.h
+++ b/bpf_progs/dscpPolicy.h
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
+#define CACHE_MAP_SIZE 1024
 #define MAX_POLICIES 16
-#define MAP_A 1
-#define MAP_B 2
 
 #define SRC_IP_MASK_FLAG     1
 #define DST_IP_MASK_FLAG     2
@@ -51,7 +50,7 @@
     __be16 dst_port_start;
     __be16 dst_port_end;
     uint8_t proto;
-    uint8_t dscp_val;
+    int8_t dscp_val;  // -1 none, or 0..63 DSCP value
     uint8_t present_fields;
     uint8_t pad[3];
 } DscpPolicy;
@@ -64,7 +63,7 @@
     __be16 src_port;
     __be16 dst_port;
     __u8 proto;
-    __u8 dscp_val;
+    __s8 dscp_val;  // -1 none, or 0..63 DSCP value
     __u8 pad[2];
 } RuleEntry;
-STRUCT_SIZE(RuleEntry, 2 * 16 + 1 * 4 + 2 * 2 + 2 * 1 + 2);  // 44
\ No newline at end of file
+STRUCT_SIZE(RuleEntry, 2 * 16 + 1 * 4 + 2 * 2 + 2 * 1 + 2);  // 44
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 80477f1..c76416f 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -142,3 +142,8 @@
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
 }
+
+platform_compat_config {
+    name: "connectivity-t-platform-compat-config",
+    src: ":framework-connectivity-t",
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 3fcc11b..fb3b1d6 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -139,17 +139,21 @@
      * The platform will only keep the daemon running as long as there are
      * any legacy apps connected.
      *
-     * After Android 12, directly communicate with native daemon might not
-     * work since the native damon won't always stay alive.
-     * Use the NSD APIs from NsdManager as the replacement is recommended.
-     * An another alternative could be bundling your own mdns solutions instead of
+     * After Android 12, direct communication with the native daemon might not work since the native
+     * daemon won't always stay alive. Using the NSD APIs from NsdManager as the replacement is
+     * recommended.
+     * Another alternative could be bundling your own mdns solutions instead of
      * depending on the system mdns native daemon.
      *
+     * This compatibility change applies to Android 13 and later only. To toggle behavior on
+     * Android 12 and Android 12L, use RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS.
+     *
      * @hide
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
-    public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS = 191844585L;
+    // This was a platform change ID with value 191844585L before T
+    public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER = 235355681L;
 
     /**
      * Broadcast intent action to indicate whether network service discovery is
@@ -500,7 +504,7 @@
 
         // Only proactively start the daemon if the target SDK < S, otherwise the internal service
         // would automatically start/stop the native daemon as needed.
-        if (!CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
+        if (!CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)) {
             try {
                 mService.startDaemon();
             } catch (RemoteException e) {
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 857ece5..38e0059 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -53,7 +53,7 @@
     return static_cast<T>(res);
 }
 
-static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
+static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jclass clazz, jobject javaFd)
 {
     struct sock_filter filter_code[] = {
         // Reject all.
@@ -71,7 +71,7 @@
     }
 }
 
-static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
+static void android_net_utils_detachBPFFilter(JNIEnv *env, jclass clazz, jobject javaFd)
 {
     int optval_ignored = 0;
     int fd = AFileDescriptor_getFd(env, javaFd);
@@ -82,13 +82,13 @@
     }
 }
 
-static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jclass clazz,
         jlong netHandle)
 {
     return (jboolean) !android_setprocnetwork(netHandle);
 }
 
-static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jclass clazz)
 {
     net_handle_t network;
     if (android_getprocnetwork(&network) != 0) {
@@ -99,13 +99,13 @@
     return (jlong) network;
 }
 
-static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
+static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jclass clazz,
         jint netId, jlong netHandle)
 {
     return (jboolean) !android_setprocdns(netHandle);
 }
 
-static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
+static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jclass clazz, jobject javaFd,
                                                   jlong netHandle) {
     return android_setsocknetwork(netHandle, AFileDescriptor_getFd(env, javaFd));
 }
@@ -119,7 +119,7 @@
     return true;
 }
 
-static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle,
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jclass clazz, jlong netHandle,
         jstring dname, jint ns_class, jint ns_type, jint flags) {
     const jsize javaCharsCount = env->GetStringLength(dname);
     const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
@@ -140,7 +140,7 @@
     return jniCreateFileDescriptor(env, fd);
 }
 
-static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle,
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jclass clazz, jlong netHandle,
         jbyteArray msg, jint msgLen, jint flags) {
     uint8_t data[MAXCMDSIZE];
 
@@ -155,7 +155,7 @@
     return jniCreateFileDescriptor(env, fd);
 }
 
-static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+static jobject android_net_utils_resNetworkResult(JNIEnv *env, jclass clazz, jobject javaFd) {
     int fd = AFileDescriptor_getFd(env, javaFd);
     int rcode;
     uint8_t buf[MAXPACKETSIZE] = {0};
@@ -181,13 +181,13 @@
     return env->NewObject(class_DnsResponse, ctor, answer, rcode);
 }
 
-static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
+static void android_net_utils_resNetworkCancel(JNIEnv *env, jclass clazz, jobject javaFd) {
     int fd = AFileDescriptor_getFd(env, javaFd);
     android_res_cancel(fd);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
 
-static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
+static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jclass clazz) {
     net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED;
     if (int res = android_getprocdns(&dnsNetHandle) < 0) {
         jniThrowErrnoException(env, "getDnsNetwork", -res);
@@ -204,7 +204,7 @@
             static_cast<jlong>(dnsNetHandle));
 }
 
-static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
+static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jclass clazz, jobject javaFd) {
     if (javaFd == NULL) {
         jniThrowNullPointerException(env, NULL);
         return NULL;
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index b628251..21d1a00 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -164,7 +164,7 @@
     }
 
     public NetworkStatsFactory(@NonNull Context ctx) {
-        this(ctx, new File("/proc/"), true, new BpfNetMaps());
+        this(ctx, new File("/proc/"), true, new BpfNetMaps(ctx));
     }
 
     @VisibleForTesting
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 0ff8810..3befd86 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -31,9 +31,11 @@
 import static android.system.OsConstants.ENOENT;
 import static android.system.OsConstants.EOPNOTSUPP;
 
+import android.content.Context;
 import android.net.INetd;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.provider.DeviceConfig;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -42,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.Struct.U32;
 
 import java.io.FileDescriptor;
@@ -70,6 +73,10 @@
     // Use legacy netd for releases before T.
     private static boolean sInitialized = false;
 
+    private static Boolean sEnableJavaBpfMap = null;
+    private static final String BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP =
+            "bpf_net_maps_enable_java_bpf_map";
+
     // Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
     // This entry is not accessed by others.
     // BpfNetMaps acquires this lock while sequence of read, modify, and write.
@@ -101,6 +108,14 @@
     // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
 
     /**
+     * Set sEnableJavaBpfMap for test.
+     */
+    @VisibleForTesting
+    public static void setEnableJavaBpfMapForTest(boolean enable) {
+        sEnableJavaBpfMap = enable;
+    }
+
+    /**
      * Set configurationMap for test.
      */
     @VisibleForTesting
@@ -147,8 +162,15 @@
      * Initializes the class if it is not already initialized. This method will open maps but not
      * cause any other effects. This method may be called multiple times on any thread.
      */
-    private static synchronized void ensureInitialized() {
+    private static synchronized void ensureInitialized(final Context context) {
         if (sInitialized) return;
+        if (sEnableJavaBpfMap == null) {
+            sEnableJavaBpfMap = DeviceConfigUtils.isFeatureEnabled(context,
+                    DeviceConfig.NAMESPACE_TETHERING, BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP,
+                    SdkLevel.isAtLeastU() /* defaultValue */);
+        }
+        Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap);
+
         setBpfMaps();
         native_init();
         sInitialized = true;
@@ -168,20 +190,20 @@
     }
 
     /** Constructor used after T that doesn't need to use netd anymore. */
-    public BpfNetMaps() {
-        this(null);
+    public BpfNetMaps(final Context context) {
+        this(context, null);
 
         if (PRE_T) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
     }
 
-    public BpfNetMaps(final INetd netd) {
-        this(netd, new Dependencies());
+    public BpfNetMaps(final Context context, final INetd netd) {
+        this(context, netd, new Dependencies());
     }
 
     @VisibleForTesting
-    public BpfNetMaps(final INetd netd, final Dependencies deps) {
+    public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps) {
         if (!PRE_T) {
-            ensureInitialized();
+            ensureInitialized(context);
         }
         mNetd = netd;
         mDeps = deps;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ae1f808..7b5026d 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1399,8 +1399,8 @@
          * @param netd
          * @return BpfNetMaps implementation.
          */
-        public BpfNetMaps getBpfNetMaps(INetd netd) {
-            return new BpfNetMaps(netd);
+        public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
+            return new BpfNetMaps(context, netd);
         }
 
         /**
@@ -1529,7 +1529,7 @@
         mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
-        mBpfNetMaps = mDeps.getBpfNetMaps(netd);
+        mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd);
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
@@ -3604,11 +3604,12 @@
 
             switch (msg.what) {
                 case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
-                    final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
-                            (NetworkCapabilities) arg.second);
-                    maybeUpdateWifiRoamTimestamp(nai, networkCapabilities);
-                    processCapabilitiesFromAgent(nai, networkCapabilities);
-                    updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
+                    nai.declaredCapabilitiesUnsanitized =
+                            new NetworkCapabilities((NetworkCapabilities) arg.second);
+                    final NetworkCapabilities sanitized = sanitizedCapabilitiesFromAgent(
+                            mCarrierPrivilegeAuthenticator, nai);
+                    maybeUpdateWifiRoamTimestamp(nai, sanitized);
+                    updateCapabilities(nai.getCurrentScore(), nai, sanitized);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
@@ -7323,11 +7324,14 @@
         if (VDBG) log("Network Monitor created for " +  nai);
         // nai.nc and nai.lp are the same object that was passed by the network agent if the agent
         // lives in the same process as this code (e.g. wifi), so make sure this code doesn't
-        // mutate their object
-        final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+        // mutate their object. TODO : make this copy much earlier to avoid them mutating it
+        // while the network monitor is starting.
         final LinkProperties lp = new LinkProperties(nai.linkProperties);
-        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
-        processCapabilitiesFromAgent(nai, nc);
+        // Store a copy of the declared capabilities.
+        nai.declaredCapabilitiesUnsanitized = new NetworkCapabilities(nai.networkCapabilities);
+        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info said.
+        final NetworkCapabilities nc =
+                sanitizedCapabilitiesFromAgent(mCarrierPrivilegeAuthenticator, nai);
         nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
         processLinkPropertiesFromAgent(nai, lp);
         nai.linkProperties = lp;
@@ -7792,28 +7796,35 @@
     }
 
     /**
-     * Called when receiving NetworkCapabilities directly from a NetworkAgent.
-     * Stores into |nai| any data coming from the agent that might also be written to the network's
-     * NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
-     * agent is not lost when updateCapabilities is called.
+     * Sanitize capabilities coming from a network agent.
+     *
+     * Agents have restrictions on what capabilities they can send to Connectivity. For example,
+     * they can't change the owner UID from what they declared before, and complex restrictions
+     * apply to the accessUids field.
+     * They also should not mutate immutable capabilities, although for backward-compatibility
+     * this is not enforced and limited to just a log.
+     *
+     * This method returns a sanitized copy of the passed capabilities to make sure they don't
+     * contain stuff they should not, and should generally be called by code that accesses
+     * {@link NetworkAgentInfo#declaredCapabilitiesUnsanitized}.
      */
-    private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
+    // TODO : move this to NetworkAgentInfo
+    private NetworkCapabilities sanitizedCapabilitiesFromAgent(
+            final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator,
+            @NonNull final NetworkAgentInfo nai) {
+        final NetworkCapabilities nc = new NetworkCapabilities(nai.declaredCapabilitiesUnsanitized);
         if (nc.hasConnectivityManagedCapability()) {
             Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
         }
-        // Note: resetting the owner UID before storing the agent capabilities in NAI means that if
-        // the agent attempts to change the owner UID, then nai.declaredCapabilities will not
-        // actually be the same as the capabilities sent by the agent. Still, it is safer to reset
-        // the owner UID here and behave as if the agent had never tried to change it.
         if (nai.networkCapabilities.getOwnerUid() != nc.getOwnerUid()) {
             Log.e(TAG, nai.toShortString() + ": ignoring attempt to change owner from "
                     + nai.networkCapabilities.getOwnerUid() + " to " + nc.getOwnerUid());
             nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
         }
-        nai.declaredCapabilities = new NetworkCapabilities(nc);
         NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE),
-                mCarrierPrivilegeAuthenticator);
+                carrierPrivilegeAuthenticator);
+        return nc;
     }
 
     /** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -7940,7 +7951,8 @@
         }
 
         if (nai.propagateUnderlyingCapabilities()) {
-            applyUnderlyingCapabilities(nai.declaredUnderlyingNetworks, nai.declaredCapabilities,
+            applyUnderlyingCapabilities(nai.declaredUnderlyingNetworks,
+                    sanitizedCapabilitiesFromAgent(mCarrierPrivilegeAuthenticator, nai),
                     newNc);
         }
 
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 8cb3213..2bfad10 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -185,7 +185,7 @@
                         new DscpPolicyValue(policy.getSourceAddress(),
                             policy.getDestinationAddress(), ifIndex,
                             policy.getSourcePort(), policy.getDestinationPortRange(),
-                            (short) policy.getProtocol(), (short) policy.getDscpValue()));
+                            (short) policy.getProtocol(), (byte) policy.getDscpValue()));
             }
 
             // Add v6 policy to mBpfDscpIpv6Policies if source and destination address
@@ -196,7 +196,7 @@
                         new DscpPolicyValue(policy.getSourceAddress(),
                                 policy.getDestinationAddress(), ifIndex,
                                 policy.getSourcePort(), policy.getDestinationPortRange(),
-                                (short) policy.getProtocol(), (short) policy.getDscpValue()));
+                                (short) policy.getProtocol(), (byte) policy.getDscpValue()));
             }
 
             ifacePolicies.put(policy.getPolicyId(), addIndex);
diff --git a/service/src/com/android/server/connectivity/DscpPolicyValue.java b/service/src/com/android/server/connectivity/DscpPolicyValue.java
index 6e4e7eb..4bb41da 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyValue.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyValue.java
@@ -52,8 +52,8 @@
     @Field(order = 6, type = Type.U8)
     public final short proto;
 
-    @Field(order = 7, type = Type.U8)
-    public final short dscp;
+    @Field(order = 7, type = Type.S8)
+    public final byte dscp;
 
     @Field(order = 8, type = Type.U8, padding = 3)
     public final short mask;
@@ -100,7 +100,7 @@
             InetAddress.parseNumericAddress("::").getAddress();
 
     private short makeMask(final byte[] src46, final byte[] dst46, final int srcPort,
-            final int dstPortStart, final short proto, final short dscp) {
+            final int dstPortStart, final short proto, final byte dscp) {
         short mask = 0;
         if (src46 != EMPTY_ADDRESS_FIELD) {
             mask |= SRC_IP_MASK;
@@ -122,7 +122,7 @@
 
     private DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
             final int srcPort, final int dstPortStart, final int dstPortEnd, final short proto,
-            final short dscp) {
+            final byte dscp) {
         this.src46 = toAddressField(src46);
         this.dst46 = toAddressField(dst46);
         this.ifIndex = ifIndex;
@@ -142,7 +142,7 @@
 
     public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
             final int srcPort, final Range<Integer> dstPort, final short proto,
-            final short dscp) {
+            final byte dscp) {
         this(src46, dst46, ifIndex, srcPort, dstPort != null ? dstPort.getLower() : -1,
                 dstPort != null ? dstPort.getUpper() : -1, proto, dscp);
     }
@@ -150,7 +150,7 @@
     public static final DscpPolicyValue NONE = new DscpPolicyValue(
             null /* src46 */, null /* dst46 */, 0 /* ifIndex */, -1 /* srcPort */,
             -1 /* dstPortStart */, -1 /* dstPortEnd */, (short) -1 /* proto */,
-            (short) 0 /* dscp */);
+            (byte) -1 /* dscp */);
 
     @Override
     public String toString() {
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b40b6e0..04031af 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -181,8 +181,12 @@
 
     // The capabilities originally announced by the NetworkAgent, regardless of any capabilities
     // that were added or removed due to this network's underlying networks.
-    // Only set if #propagateUnderlyingCapabilities is true.
-    public @Nullable NetworkCapabilities declaredCapabilities;
+    //
+    // As the name implies, these capabilities are not sanitized and are not to
+    // be trusted. Most callers should simply use the {@link networkCapabilities}
+    // field instead, and callers who need the declared capabilities should generally
+    // pass these to {@link ConnectivityService#sanitizedCapabilitiesFromAgent} before using them.
+    public @Nullable NetworkCapabilities declaredCapabilitiesUnsanitized;
 
     // Indicates if netd has been told to create this Network. From this point on the appropriate
     // routing rules are setup and routes are added so packets can begin flowing over the Network.
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 62f37bb..a6179fc 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -128,3 +128,18 @@
     ],
 }
 
+android_test {
+    name: "CtsNetTestCasesMaxTargetSdk30",  // Must match CtsNetTestCasesMaxTargetSdk30 annotation.
+    defaults: [
+        "CtsNetTestCasesDefaults",
+        "CtsNetTestCasesApiStableDefaults",
+    ],
+    target_sdk_version: "30",
+    package_name: "android.net.cts.maxtargetsdk30",  // CTS package names must be unique.
+    instrumentation_target_package: "android.net.cts.maxtargetsdk30",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-networking",
+    ],
+}
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index b68d3bf..77dca95 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -67,6 +67,7 @@
 import com.android.net.module.util.structs.EthernetHeader
 import com.android.testutils.ArpResponder
 import com.android.testutils.CompatUtil
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.assertParcelingIsLossless
 import com.android.testutils.RouterAdvertisementResponder
@@ -107,6 +108,7 @@
 
 @AppModeFull(reason = "Instant apps cannot create test networks")
 @RunWith(AndroidJUnit4::class)
+@ConnectivityModuleTest
 class DscpPolicyTest {
     @JvmField
     @Rule
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 64cc97d..a02be85 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -16,6 +16,7 @@
 package android.net.cts
 
 import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.app.compat.CompatChanges
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
 import android.net.LinkProperties
@@ -46,6 +47,7 @@
 import android.net.nsd.NsdManager.RegistrationListener
 import android.net.nsd.NsdManager.ResolveListener
 import android.net.nsd.NsdServiceInfo
+import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Process.myTid
@@ -56,17 +58,23 @@
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.TrackRecord
 import com.android.networkstack.apishim.NsdShimImpl
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.TestableNetworkAgent
 import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
 import org.junit.After
 import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.io.File
 import java.net.ServerSocket
 import java.nio.charset.StandardCharsets
 import java.util.Random
@@ -89,6 +97,10 @@
 @AppModeFull(reason = "Socket cannot bind in instant app mode")
 @RunWith(AndroidJUnit4::class)
 class NsdManagerTest {
+    // Rule used to filter CtsNetTestCasesMaxTargetSdkXX
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
+
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
     private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
 
@@ -692,6 +704,30 @@
         }
     }
 
+    @Test @CtsNetTestCasesMaxTargetSdk30("Socket is started with the service up to target SDK 30")
+    fun testManagerCreatesLegacySocket() {
+        nsdManager // Ensure the lazy-init member is initialized, so NsdManager is created
+        val socket = File("/dev/socket/mdnsd")
+        val timeout = System.currentTimeMillis() + TIMEOUT_MS
+        while (!socket.exists() && System.currentTimeMillis() < timeout) {
+            Thread.sleep(10)
+        }
+        assertTrue("$socket was not found after $TIMEOUT_MS ms", socket.exists())
+    }
+
+    // The compat change is part of a connectivity module update that applies to T+
+    @ConnectivityModuleTest @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @Test @CtsNetTestCasesMaxTargetSdk30("Socket is started with the service up to target SDK 30")
+    fun testManagerCreatesLegacySocket_CompatChange() {
+        // The socket may have been already created by some other app, or some other test, in which
+        // case this test cannot verify creation. At least verify that the compat change is
+        // disabled in a process with max SDK 30; unit tests already verify that start is requested
+        // when the compat change is disabled.
+        // Note that before T the compat constant had a different int value.
+        assertFalse(CompatChanges.isChangeEnabled(
+                NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER))
+    }
+
     /**
      * Register a service and return its registration record.
      */
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index efc24d3..73e4c0e 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -209,7 +209,7 @@
         doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
         doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
         doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
-        doReturn(mock(BpfNetMaps::class.java)).`when`(deps).getBpfNetMaps(any())
+        doReturn(mock(BpfNetMaps::class.java)).`when`(deps).getBpfNetMaps(any(), any())
         doAnswer { inv ->
             object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1),
                     inv.getArgument(2)) {
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 0911ecf..c7e8b97 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -88,12 +88,8 @@
     SHARED "map_clatd_clat_egress4_map",
     SHARED "map_clatd_clat_ingress6_map",
     SHARED "map_dscpPolicy_ipv4_dscp_policies_map",
-    SHARED "map_dscpPolicy_ipv4_socket_to_policies_map_A",
-    SHARED "map_dscpPolicy_ipv4_socket_to_policies_map_B",
     SHARED "map_dscpPolicy_ipv6_dscp_policies_map",
-    SHARED "map_dscpPolicy_ipv6_socket_to_policies_map_A",
-    SHARED "map_dscpPolicy_ipv6_socket_to_policies_map_B",
-    SHARED "map_dscpPolicy_switch_comp_map",
+    SHARED "map_dscpPolicy_socket_policy_cache_map",
     NETD "map_netd_app_uid_stats_map",
     NETD "map_netd_configuration_map",
     NETD "map_netd_cookie_tag_map",
diff --git a/tests/native/Android.bp b/tests/native/connectivity_native_test/Android.bp
similarity index 100%
rename from tests/native/Android.bp
rename to tests/native/connectivity_native_test/Android.bp
diff --git a/tests/native/AndroidTestTemplate.xml b/tests/native/connectivity_native_test/AndroidTestTemplate.xml
similarity index 100%
rename from tests/native/AndroidTestTemplate.xml
rename to tests/native/connectivity_native_test/AndroidTestTemplate.xml
diff --git a/tests/native/NetNativeTestConfigTemplate.xml b/tests/native/connectivity_native_test/NetNativeTestConfigTemplate.xml
similarity index 100%
rename from tests/native/NetNativeTestConfigTemplate.xml
rename to tests/native/connectivity_native_test/NetNativeTestConfigTemplate.xml
diff --git a/tests/native/OWNERS b/tests/native/connectivity_native_test/OWNERS
similarity index 100%
rename from tests/native/OWNERS
rename to tests/native/connectivity_native_test/OWNERS
diff --git a/tests/native/connectivity_native_test.cpp b/tests/native/connectivity_native_test/connectivity_native_test.cpp
similarity index 100%
rename from tests/native/connectivity_native_test.cpp
rename to tests/native/connectivity_native_test/connectivity_native_test.cpp
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 32274bc..e3dbb14 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -81,70 +81,70 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testResolveServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestResolveService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testResolveServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestResolveService();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testDiscoverServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestDiscoverService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testDiscoverServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestDiscoverService();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testParallelResolveServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestParallelResolveService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testParallelResolveServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestParallelResolveService();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testInvalidCallsS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestInvalidCalls();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testInvalidCallsPreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestInvalidCalls();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testRegisterServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestRegisterService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testRegisterServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestRegisterService();
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 61d9eea..2d09bf2 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -46,6 +46,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.net.INetd;
 import android.os.Build;
 import android.os.ServiceSpecificException;
@@ -102,6 +103,7 @@
 
     @Mock INetd mNetd;
     @Mock BpfNetMaps.Dependencies mDeps;
+    @Mock Context mContext;
     private final BpfMap<U32, U32> mConfigurationMap = new TestBpfMap<>(U32.class, U32.class);
     private final BpfMap<U32, UidOwnerValue> mUidOwnerMap =
             new TestBpfMap<>(U32.class, UidOwnerValue.class);
@@ -110,9 +112,10 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+        BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
         BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
         BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
-        mBpfNetMaps = new BpfNetMaps(mNetd, mDeps);
+        mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 3264a36..62073cd 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2061,7 +2061,7 @@
         }
 
         @Override
-        public BpfNetMaps getBpfNetMaps(INetd netd) {
+        public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
             return mBpfNetMaps;
         }
 
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 07884cf..1813393 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -159,7 +159,7 @@
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testPreSClients() throws Exception {
         // Pre S client connected, the daemon should be started.
         connectClient(mService);
@@ -186,7 +186,7 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testNoDaemonStartedWhenClientsConnect() throws Exception {
         // Creating an NsdManager will not cause daemon startup.
         connectClient(mService);
@@ -220,7 +220,7 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testClientRequestsAreGCedAtDisconnection() throws Exception {
         final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
@@ -263,7 +263,7 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
+    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testCleanupDelayNoRequestActive() throws Exception {
         final NsdManager client = connectClient(mService);