Merge "[XFRM_MSG_GETSA] Fix the class names and code locations" into main
diff --git a/OWNERS b/OWNERS
index b2176cc..988af41 100644
--- a/OWNERS
+++ b/OWNERS
@@ -3,3 +3,4 @@
 file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
 
 per-file **IpSec* = file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
+per-file **Xfrm* = file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 4bd7e6a..9f542f4 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -90,7 +90,6 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1258,12 +1257,18 @@
         pw.decreaseIndent();
     }
 
-    private IpPrefix longToPrefix(long ip64) {
-        final ByteBuffer prefixBuffer = ByteBuffer.allocate(IPV6_ADDR_LEN);
-        prefixBuffer.putLong(ip64);
+    /**
+     * Returns a /64 IpPrefix corresponding to the passed in byte array
+     *
+     * @param ip64 byte array to convert format
+     * @return the converted IpPrefix
+     */
+    @VisibleForTesting
+    public static IpPrefix bytesToPrefix(final byte[] ip64) {
         IpPrefix sourcePrefix;
+        byte[] prefixBytes = Arrays.copyOf(ip64, IPV6_ADDR_LEN);
         try {
-            sourcePrefix = new IpPrefix(InetAddress.getByAddress(prefixBuffer.array()), 64);
+            sourcePrefix = new IpPrefix(InetAddress.getByAddress(prefixBytes), 64);
         } catch (UnknownHostException e) {
             // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte array
             // is the wrong length, but we allocate it with fixed length IPV6_ADDR_LEN.
@@ -1274,7 +1279,7 @@
 
     private String ipv6UpstreamRuleToString(TetherUpstream6Key key, Tether6Value value) {
         return String.format("%d(%s) [%s] [%s] -> %d(%s) %04x [%s] [%s]",
-                key.iif, getIfName(key.iif), key.dstMac, longToPrefix(key.src64), value.oif,
+                key.iif, getIfName(key.iif), key.dstMac, bytesToPrefix(key.src64), value.oif,
                 getIfName(value.oif), value.ethProto, value.ethSrcMac, value.ethDstMac);
     }
 
@@ -1570,7 +1575,7 @@
          */
         @NonNull
         public TetherUpstream6Key makeTetherUpstream6Key() {
-            long prefix64 = ByteBuffer.wrap(sourcePrefix.getRawAddress()).getLong();
+            final byte[] prefix64 = Arrays.copyOf(sourcePrefix.getRawAddress(), 8);
             return new TetherUpstream6Key(downstreamIfindex, inDstMac, prefix64);
         }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
index 36a1c3c..0cc3dd9 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
@@ -32,10 +32,10 @@
     @Field(order = 1, type = Type.EUI48, padding = 6)
     public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress).
 
-    @Field(order = 2, type = Type.S64)
-    public final long src64; // The top 64-bits of the source ip.
+    @Field(order = 2, type = Type.ByteArray, arraysize = 8)
+    public final byte[] src64; // The top 64-bits of the source ip.
 
-    public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac, long src64) {
+    public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac, final byte[] src64) {
         Objects.requireNonNull(dstMac);
 
         this.iif = iif;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 996ee11..3277363 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -327,8 +327,10 @@
                         return mConfig;
                     }
                 });
-        mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMainSM, mLog,
-                TetherMainSM.EVENT_UPSTREAM_CALLBACK);
+        mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mHandler, mLog,
+                (what, obj) -> {
+                    mTetherMainSM.sendMessage(TetherMainSM.EVENT_UPSTREAM_CALLBACK, what, 0, obj);
+                });
         mForwardedDownstreams = new HashSet<>();
 
         IntentFilter filter = new IntentFilter();
@@ -2879,4 +2881,9 @@
             } catch (RemoteException e) { }
         });
     }
+
+    @VisibleForTesting
+    public TetherMainSM getTetherMainSMForTesting() {
+        return mTetherMainSM;
+    }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index c274165..d02e8e8 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -35,7 +35,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
-import com.android.internal.util.StateMachine;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.SdkUtil.LateSdk;
 import com.android.net.module.util.SharedLog;
@@ -84,9 +83,9 @@
     /**
      * Get a reference to the UpstreamNetworkMonitor to be used by tethering.
      */
-    public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
-            SharedLog log, int what) {
-        return new UpstreamNetworkMonitor(ctx, target, log, what);
+    public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, Handler h,
+            SharedLog log, UpstreamNetworkMonitor.EventListener listener) {
+        return new UpstreamNetworkMonitor(ctx, h, log, listener);
     }
 
     /**
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index ac2aa7b..7a05d74 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -44,7 +44,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.StateMachine;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
 import com.android.networkstack.apishim.common.ConnectivityManagerShim;
@@ -111,9 +110,8 @@
 
     private final Context mContext;
     private final SharedLog mLog;
-    private final StateMachine mTarget;
     private final Handler mHandler;
-    private final int mWhat;
+    private final EventListener mEventListener;
     private final HashMap<Network, UpstreamNetworkState> mNetworkMap = new HashMap<>();
     private HashSet<IpPrefix> mLocalPrefixes;
     private ConnectivityManager mCM;
@@ -135,12 +133,11 @@
     private Network mDefaultInternetNetwork;
     private boolean mPreferTestNetworks;
 
-    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
+    public UpstreamNetworkMonitor(Context ctx, Handler h, SharedLog log, EventListener listener) {
         mContext = ctx;
-        mTarget = tgt;
-        mHandler = mTarget.getHandler();
+        mHandler = h;
         mLog = log.forSubComponent(TAG);
-        mWhat = what;
+        mEventListener = listener;
         mLocalPrefixes = new HashSet<>();
         mIsDefaultCellularUpstream = false;
         mCM = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -374,11 +371,12 @@
                     network, newNc));
         }
 
-        mNetworkMap.put(network, new UpstreamNetworkState(
-                prev.linkProperties, newNc, network));
+        final UpstreamNetworkState uns =
+                new UpstreamNetworkState(prev.linkProperties, newNc, network);
+        mNetworkMap.put(network, uns);
         // TODO: If sufficient information is available to select a more
         // preferable upstream, do so now and notify the target.
-        notifyTarget(EVENT_ON_CAPABILITIES, network);
+        mEventListener.onUpstreamEvent(EVENT_ON_CAPABILITIES, uns);
     }
 
     private @Nullable UpstreamNetworkState updateLinkProperties(@NonNull Network network,
@@ -411,7 +409,7 @@
     private void handleLinkProp(Network network, LinkProperties newLp) {
         final UpstreamNetworkState ns = updateLinkProperties(network, newLp);
         if (ns != null) {
-            notifyTarget(EVENT_ON_LINKPROPERTIES, ns);
+            mEventListener.onUpstreamEvent(EVENT_ON_LINKPROPERTIES, ns);
         }
     }
 
@@ -438,7 +436,7 @@
         // preferable upstream, do so now and notify the target.  Likewise,
         // if the current upstream network is gone, notify the target of the
         // fact that we now have no upstream at all.
-        notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
+        mEventListener.onUpstreamEvent(EVENT_ON_LOST, mNetworkMap.remove(network));
     }
 
     private void maybeHandleNetworkSwitch(@NonNull Network network) {
@@ -456,14 +454,14 @@
         // Default network changed. Update local data and notify tethering.
         Log.d(TAG, "New default Internet network: " + network);
         mDefaultInternetNetwork = network;
-        notifyTarget(EVENT_DEFAULT_SWITCHED, ns);
+        mEventListener.onUpstreamEvent(EVENT_DEFAULT_SWITCHED, ns);
     }
 
     private void recomputeLocalPrefixes() {
         final HashSet<IpPrefix> localPrefixes = allLocalPrefixes(mNetworkMap.values());
         if (!mLocalPrefixes.equals(localPrefixes)) {
             mLocalPrefixes = localPrefixes;
-            notifyTarget(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone());
+            mEventListener.onUpstreamEvent(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone());
         }
     }
 
@@ -502,12 +500,13 @@
                 // onLinkPropertiesChanged right after this method and mDefaultInternetNetwork will
                 // be updated then.
                 //
-                // Technically, not updating here isn't necessary, because the notifications to
-                // Tethering sent by notifyTarget are messages sent to a state machine running on
-                // the same thread as this method, and so cannot arrive until after this method has
-                // returned. However, it is not a good idea to rely on that because fact that
-                // Tethering uses multiple state machines running on the same thread is a major
-                // source of race conditions and something that should be fixed.
+                // Technically, mDefaultInternetNetwork could be updated here, because the
+                // Callback#onChange implementation sends messages to the state machine running
+                // on the same thread as this method. If there is new default network change,
+                // the message cannot arrive until onLinkPropertiesChanged returns.
+                // However, it is not a good idea to rely on that because fact that Tethering uses
+                // multiple state machines running on the same thread is a major source of race
+                // conditions and something that should be fixed.
                 //
                 // TODO: is it correct that this code always updates EntitlementManager?
                 // This code runs when the default network connects or changes capabilities, but the
@@ -551,7 +550,7 @@
                 mIsDefaultCellularUpstream = false;
                 mEntitlementMgr.notifyUpstream(false);
                 Log.d(TAG, "Lost default Internet network: " + network);
-                notifyTarget(EVENT_DEFAULT_SWITCHED, null);
+                mEventListener.onUpstreamEvent(EVENT_DEFAULT_SWITCHED, null);
                 return;
             }
 
@@ -569,14 +568,6 @@
         if (cb != null) cm().unregisterNetworkCallback(cb);
     }
 
-    private void notifyTarget(int which, Network network) {
-        notifyTarget(which, mNetworkMap.get(network));
-    }
-
-    private void notifyTarget(int which, Object obj) {
-        mTarget.sendMessage(mWhat, which, 0, obj);
-    }
-
     private static class TypeStatePair {
         public int type = TYPE_NONE;
         public UpstreamNetworkState ns = null;
@@ -698,4 +689,10 @@
     public void setPreferTestNetworks(boolean prefer) {
         mPreferTestNetworks = prefer;
     }
+
+    /** An interface to notify upstream network changes. */
+    public interface EventListener {
+        /** Notify the client of some event */
+        void onUpstreamEvent(int what, Object obj);
+    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 6c1721e..01600b8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -64,6 +64,7 @@
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
 import static com.android.testutils.MiscAsserts.assertSameElements;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -166,7 +167,6 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -794,7 +794,7 @@
         if (!mDeps.isAtLeastS()) return;
         ArrayMap<TetherUpstream6Key, Tether6Value> expected = new ArrayMap<>();
         for (IpPrefix upstreamPrefix : upstreamPrefixes) {
-            long prefix64 = prefixToLong(upstreamPrefix);
+            final byte[] prefix64 = prefixToIp64(upstreamPrefix);
             final TetherUpstream6Key key = new TetherUpstream6Key(DOWNSTREAM_IFACE_PARAMS.index,
                     DOWNSTREAM_IFACE_PARAMS.macAddr, prefix64);
             final Tether6Value value = new Tether6Value(upstreamIfindex,
@@ -822,7 +822,7 @@
         if (!mDeps.isAtLeastS()) return;
         Set<TetherUpstream6Key> expected = new ArraySet<>();
         for (IpPrefix upstreamPrefix : upstreamPrefixes) {
-            long prefix64 = prefixToLong(upstreamPrefix);
+            final byte[] prefix64 = prefixToIp64(upstreamPrefix);
             final TetherUpstream6Key key = new TetherUpstream6Key(DOWNSTREAM_IFACE_PARAMS.index,
                     DOWNSTREAM_IFACE_PARAMS.macAddr, prefix64);
             expected.add(key);
@@ -1275,7 +1275,7 @@
     }
 
     @Test
-    public void testRuleMakeTetherDownstream6Key() throws Exception {
+    public void testIpv6DownstreamRuleMakeTetherDownstream6Key() throws Exception {
         final int mobileIfIndex = 100;
         final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
 
@@ -1288,7 +1288,7 @@
     }
 
     @Test
-    public void testRuleMakeTether6Value() throws Exception {
+    public void testIpv6DownstreamRuleMakeTether6Value() throws Exception {
         final int mobileIfIndex = 100;
         final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
 
@@ -1303,6 +1303,46 @@
     }
 
     @Test
+    public void testIpv6UpstreamRuleMakeTetherUpstream6Key() {
+        final byte[] bytes = new byte[]{(byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0xab, (byte) 0xcd, (byte) 0xfe, (byte) 0x00};
+        final IpPrefix prefix = new IpPrefix("2001:db8:abcd:fe00::/64");
+        final Ipv6UpstreamRule rule = buildTestUpstreamRule(UPSTREAM_IFINDEX,
+                DOWNSTREAM_IFINDEX, prefix, DOWNSTREAM_MAC);
+
+        final TetherUpstream6Key key = rule.makeTetherUpstream6Key();
+        assertEquals(DOWNSTREAM_IFINDEX, key.iif);
+        assertEquals(DOWNSTREAM_MAC, key.dstMac);
+        assertArrayEquals(bytes, key.src64);
+        // iif (4) + dstMac (6) + padding (6) + src64 (8) = 24
+        assertEquals(24, key.writeToBytes().length);
+    }
+
+    @Test
+    public void testIpv6UpstreamRuleMakeTether6Value() {
+        final IpPrefix prefix = new IpPrefix("2001:db8:abcd:fe00::/64");
+        final Ipv6UpstreamRule rule = buildTestUpstreamRule(UPSTREAM_IFINDEX,
+                DOWNSTREAM_IFINDEX, prefix, DOWNSTREAM_MAC);
+
+        final Tether6Value value = rule.makeTether6Value();
+        assertEquals(UPSTREAM_IFINDEX, value.oif);
+        assertEquals(MAC_NULL, value.ethDstMac);
+        assertEquals(MAC_NULL, value.ethSrcMac);
+        assertEquals(ETH_P_IPV6, value.ethProto);
+        assertEquals(NetworkStackConstants.ETHER_MTU, value.pmtu);
+        // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20
+        assertEquals(20, value.writeToBytes().length);
+    }
+
+    @Test
+    public void testBytesToPrefix() {
+        final byte[] bytes = new byte[]{(byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x12, (byte) 0x34};
+        final IpPrefix prefix = new IpPrefix("2001:db8:0:1234::/64");
+        assertEquals(prefix, BpfCoordinator.bytesToPrefix(bytes));
+    }
+
+    @Test
     public void testSetDataLimit() throws Exception {
         setupFunctioningNetdInterface();
 
@@ -2269,8 +2309,8 @@
                 100 /* nonzero, CT_NEW */);
     }
 
-    private static long prefixToLong(IpPrefix prefix) {
-        return ByteBuffer.wrap(prefix.getRawAddress()).getLong();
+    private static byte[] prefixToIp64(IpPrefix prefix) {
+        return Arrays.copyOf(prefix.getRawAddress(), 8);
     }
 
     void checkRule4ExistInUpstreamDownstreamMap() throws Exception {
@@ -2515,7 +2555,7 @@
         final Ipv6DownstreamRule rule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
         mBpfDownstream6Map.insertEntry(rule.makeTetherDownstream6Key(), rule.makeTether6Value());
 
-        final long prefix64 = prefixToLong(UPSTREAM_PREFIX);
+        final byte[] prefix64 = prefixToIp64(UPSTREAM_PREFIX);
         final TetherUpstream6Key upstream6Key = new TetherUpstream6Key(DOWNSTREAM_IFINDEX,
                 DOWNSTREAM_MAC, prefix64);
         final Tether6Value upstream6Value = new Tether6Value(UPSTREAM_IFINDEX,
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 6eba590..5877fc5 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -187,7 +187,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.CollectionUtils;
@@ -310,6 +309,7 @@
     private BroadcastReceiver mBroadcastReceiver;
     private Tethering mTethering;
     private TestTetheringEventCallback mTetheringEventCallback;
+    private Tethering.TetherMainSM mTetherMainSM;
     private PhoneStateListener mPhoneStateListener;
     private InterfaceConfigurationParcel mInterfaceConfiguration;
     private TetheringConfiguration mConfig;
@@ -319,6 +319,7 @@
     private SoftApCallback mSoftApCallback;
     private SoftApCallback mLocalOnlyHotspotCallback;
     private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+    private UpstreamNetworkMonitor.EventListener mEventListener;
     private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
 
     private TestConnectivityManager mCm;
@@ -432,7 +433,6 @@
     }
 
     public class MockTetheringDependencies extends TetheringDependencies {
-        StateMachine mUpstreamNetworkMonitorSM;
         ArrayList<IpServer> mAllDownstreams;
 
         @Override
@@ -456,12 +456,12 @@
         }
 
         @Override
-        public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
-                StateMachine target, SharedLog log, int what) {
+        public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, Handler h,
+                SharedLog log, UpstreamNetworkMonitor.EventListener listener) {
             // Use a real object instead of a mock so that some tests can use a real UNM and some
             // can use a mock.
-            mUpstreamNetworkMonitorSM = target;
-            mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, target, log, what));
+            mEventListener = listener;
+            mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, h, log, listener));
             return mUpstreamNetworkMonitor;
         }
 
@@ -688,6 +688,7 @@
     private void initTetheringOnTestThread() throws Exception {
         mLooper = new TestLooper();
         mTethering = new Tethering(mTetheringDependencies);
+        mTetherMainSM = mTethering.getTetherMainSMForTesting();
         verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
         verify(mNetd).registerUnsolicitedEventListener(any());
         verifyDefaultNetworkRequestFiled();
@@ -1182,10 +1183,7 @@
         initTetheringUpstream(upstreamState);
 
         // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES.
-        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
-                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
-                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
-                0,
+        mEventListener.onUpstreamEvent(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
                 upstreamState);
         mLooper.dispatchAll();
 
@@ -2713,14 +2711,12 @@
     @Test
     public void testUpstreamNetworkChanged() throws Exception {
         initTetheringOnTestThread();
-        final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
-                mTetheringDependencies.mUpstreamNetworkMonitorSM;
         final InOrder inOrder = inOrder(mNotificationUpdater);
 
         // Gain upstream.
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         initTetheringUpstream(upstreamState);
-        stateMachine.chooseUpstreamType(true);
+        mTetherMainSM.chooseUpstreamType(true);
         mTetheringEventCallback.expectUpstreamChanged(upstreamState.network);
         inOrder.verify(mNotificationUpdater)
                 .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
@@ -2728,7 +2724,7 @@
         // Set the upstream with the same network ID but different object and the same capability.
         final UpstreamNetworkState upstreamState2 = buildMobileIPv4UpstreamState();
         initTetheringUpstream(upstreamState2);
-        stateMachine.chooseUpstreamType(true);
+        mTetherMainSM.chooseUpstreamType(true);
         // Expect that no upstream change event and capabilities changed event.
         mTetheringEventCallback.assertNoUpstreamChangeCallback();
         inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
@@ -2738,17 +2734,17 @@
         assertFalse(upstreamState3.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
         upstreamState3.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
         initTetheringUpstream(upstreamState3);
-        stateMachine.chooseUpstreamType(true);
+        mTetherMainSM.chooseUpstreamType(true);
         // Expect that no upstream change event and capabilities changed event.
         mTetheringEventCallback.assertNoUpstreamChangeCallback();
-        stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState3);
+        mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState3);
         inOrder.verify(mNotificationUpdater)
                 .onUpstreamCapabilitiesChanged(upstreamState3.networkCapabilities);
 
 
         // Lose upstream.
         initTetheringUpstream(null);
-        stateMachine.chooseUpstreamType(true);
+        mTetherMainSM.chooseUpstreamType(true);
         mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
         inOrder.verify(mNotificationUpdater).onUpstreamCapabilitiesChanged(null);
     }
@@ -2756,17 +2752,15 @@
     @Test
     public void testUpstreamCapabilitiesChanged() throws Exception {
         initTetheringOnTestThread();
-        final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
-                mTetheringDependencies.mUpstreamNetworkMonitorSM;
         final InOrder inOrder = inOrder(mNotificationUpdater);
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         initTetheringUpstream(upstreamState);
 
-        stateMachine.chooseUpstreamType(true);
+        mTetherMainSM.chooseUpstreamType(true);
         inOrder.verify(mNotificationUpdater)
                 .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
 
-        stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
+        mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
         inOrder.verify(mNotificationUpdater)
                 .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
 
@@ -2775,7 +2769,7 @@
         // Expect that capability is changed with new capability VALIDATED.
         assertFalse(upstreamState.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
         upstreamState.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
-        stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
+        mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
         inOrder.verify(mNotificationUpdater)
                 .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
 
@@ -2784,7 +2778,7 @@
         final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState(
                 upstreamState.linkProperties, upstreamState.networkCapabilities,
                 new Network(WIFI_NETID));
-        stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
+        mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
         inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
     }
 
@@ -2907,11 +2901,7 @@
             final String iface, final int transportType) {
         final UpstreamNetworkState upstream = buildV4UpstreamState(ipv4Address, network, iface,
                 transportType);
-        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
-                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
-                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
-                0,
-                upstream);
+        mEventListener.onUpstreamEvent(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, upstream);
         mLooper.dispatchAll();
     }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index e756bd3..045c0cb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -30,10 +30,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -51,27 +53,23 @@
 import android.net.NetworkRequest;
 import android.os.Build;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo;
 import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -84,8 +82,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class UpstreamNetworkMonitorTest {
-    private static final int EVENT_UNM_UPDATE = 1;
-
     private static final boolean INCLUDES = true;
     private static final boolean EXCLUDES = false;
 
@@ -102,12 +98,13 @@
     @Mock private EntitlementManager mEntitleMgr;
     @Mock private IConnectivityManager mCS;
     @Mock private SharedLog mLog;
+    @Mock private UpstreamNetworkMonitor.EventListener mListener;
 
-    private TestStateMachine mSM;
     private TestConnectivityManager mCM;
     private UpstreamNetworkMonitor mUNM;
 
     private final TestLooper mLooper = new TestLooper();
+    private InOrder mCallbackOrder;
 
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -117,17 +114,11 @@
         when(mLog.forSubComponent(anyString())).thenReturn(mLog);
         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
 
+        mCallbackOrder = inOrder(mListener);
         mCM = spy(new TestConnectivityManager(mContext, mCS));
         when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(mCM);
-        mSM = new TestStateMachine(mLooper.getLooper());
-        mUNM = new UpstreamNetworkMonitor(mContext, mSM, mLog, EVENT_UNM_UPDATE);
-    }
-
-    @After public void tearDown() throws Exception {
-        if (mSM != null) {
-            mSM.quit();
-            mSM = null;
-        }
+        mUNM = new UpstreamNetworkMonitor(mContext, new Handler(mLooper.getLooper()), mLog,
+                mListener);
     }
 
     @Test
@@ -603,14 +594,17 @@
         mCM.makeDefaultNetwork(cellAgent);
         mLooper.dispatchAll();
         verifyCurrentLinkProperties(cellAgent);
-        int messageIndex = mSM.messages.size() - 1;
+        verifyNotifyNetworkCapabilitiesChange(cellAgent.networkCapabilities);
+        verifyNotifyLinkPropertiesChange(cellLp);
+        verifyNotifyDefaultSwitch(cellAgent);
+        verifyNoMoreInteractions(mListener);
 
         addLinkAddresses(cellLp, ipv6Addr1);
         mCM.sendLinkProperties(cellAgent, false /* updateDefaultFirst */);
         mLooper.dispatchAll();
         verifyCurrentLinkProperties(cellAgent);
-        verifyNotifyLinkPropertiesChange(messageIndex);
-        messageIndex = mSM.messages.size() - 1;
+        verifyNotifyLinkPropertiesChange(cellLp);
+        verifyNoMoreInteractions(mListener);
 
         removeLinkAddresses(cellLp, ipv6Addr1);
         addLinkAddresses(cellLp, ipv6Addr2);
@@ -618,7 +612,8 @@
         mLooper.dispatchAll();
         assertEquals(cellAgent.linkProperties, mUNM.getCurrentPreferredUpstream().linkProperties);
         verifyCurrentLinkProperties(cellAgent);
-        verifyNotifyLinkPropertiesChange(messageIndex);
+        verifyNotifyLinkPropertiesChange(cellLp);
+        verifyNoMoreInteractions(mListener);
     }
 
     private void verifyCurrentLinkProperties(TestNetworkAgent agent) {
@@ -626,12 +621,33 @@
         assertEquals(agent.linkProperties, mUNM.getCurrentPreferredUpstream().linkProperties);
     }
 
-    private void verifyNotifyLinkPropertiesChange(int lastMessageIndex) {
-        assertEquals(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
-                mSM.messages.get(++lastMessageIndex).arg1);
-        assertEquals(UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES,
-                mSM.messages.get(++lastMessageIndex).arg1);
-        assertEquals(lastMessageIndex + 1, mSM.messages.size());
+    private void verifyNotifyNetworkCapabilitiesChange(final NetworkCapabilities cap) {
+        mCallbackOrder.verify(mListener).onUpstreamEvent(
+                eq(UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES),
+                argThat(uns -> uns instanceof UpstreamNetworkState
+                    && cap.equals(((UpstreamNetworkState) uns).networkCapabilities)));
+
+    }
+
+    private void verifyNotifyLinkPropertiesChange(final LinkProperties lp) {
+        mCallbackOrder.verify(mListener).onUpstreamEvent(
+                eq(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES),
+                argThat(uns -> uns instanceof UpstreamNetworkState
+                    && lp.equals(((UpstreamNetworkState) uns).linkProperties)));
+
+        mCallbackOrder.verify(mListener).onUpstreamEvent(
+                eq(UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES), any());
+    }
+
+    private void verifyNotifyDefaultSwitch(TestNetworkAgent agent) {
+        mCallbackOrder.verify(mListener).onUpstreamEvent(
+                eq(UpstreamNetworkMonitor.EVENT_DEFAULT_SWITCHED),
+                argThat(uns ->
+                    uns instanceof UpstreamNetworkState
+                    && agent.networkId.equals(((UpstreamNetworkState) uns).network)
+                    && agent.linkProperties.equals(((UpstreamNetworkState) uns).linkProperties)
+                    && agent.networkCapabilities.equals(
+                        ((UpstreamNetworkState) uns).networkCapabilities)));
     }
 
     private void addLinkAddresses(LinkProperties lp, String... addrs) {
@@ -673,33 +689,6 @@
         return false;
     }
 
-    public static class TestStateMachine extends StateMachine {
-        public final ArrayList<Message> messages = new ArrayList<>();
-        private final State mLoggingState = new LoggingState();
-
-        class LoggingState extends State {
-            @Override public void enter() {
-                messages.clear();
-            }
-
-            @Override public void exit() {
-                messages.clear();
-            }
-
-            @Override public boolean processMessage(Message msg) {
-                messages.add(msg);
-                return true;
-            }
-        }
-
-        public TestStateMachine(Looper looper) {
-            super("UpstreamNetworkMonitor.TestStateMachine", looper);
-            addState(mLoggingState);
-            setInitialState(mLoggingState);
-            super.start();
-        }
-    }
-
     static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
         final Set<String> expectedSet = new HashSet<>();
         Collections.addAll(expectedSet, expected);
diff --git a/framework-t/src/android/net/nsd/MDnsManager.java b/framework-t/src/android/net/nsd/MDnsManager.java
index c11e60c..c7ded25 100644
--- a/framework-t/src/android/net/nsd/MDnsManager.java
+++ b/framework-t/src/android/net/nsd/MDnsManager.java
@@ -51,7 +51,7 @@
     public void startDaemon() {
         try {
             mMdns.startDaemon();
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Start mdns failed.", e);
         }
     }
@@ -62,7 +62,7 @@
     public void stopDaemon() {
         try {
             mMdns.stopDaemon();
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Stop mdns failed.", e);
         }
     }
@@ -85,7 +85,7 @@
                 registrationType, port, txtRecord, interfaceIdx);
         try {
             mMdns.registerService(info);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Register service failed.", e);
             return false;
         }
@@ -105,7 +105,7 @@
                 registrationType, "" /* domainName */, interfaceIdx, NETID_UNSET);
         try {
             mMdns.discover(info);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Discover service failed.", e);
             return false;
         }
@@ -129,7 +129,7 @@
                 new byte[0] /* txtRecord */, interfaceIdx);
         try {
             mMdns.resolve(info);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Resolve service failed.", e);
             return false;
         }
@@ -149,7 +149,7 @@
                 "" /* address */, interfaceIdx, NETID_UNSET);
         try {
             mMdns.getServiceAddress(info);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Get service address failed.", e);
             return false;
         }
@@ -165,7 +165,7 @@
     public boolean stopOperation(int id) {
         try {
             mMdns.stopOperation(id);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Stop operation failed.", e);
             return false;
         }
@@ -180,7 +180,7 @@
     public void registerEventListener(@NonNull IMDnsEventListener listener) {
         try {
             mMdns.registerEventListener(listener);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Register listener failed.", e);
         }
     }
@@ -193,7 +193,7 @@
     public void unregisterEventListener(@NonNull IMDnsEventListener listener) {
         try {
             mMdns.unregisterEventListener(listener);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
             Log.e(TAG, "Unregister listener failed.", e);
         }
     }
diff --git a/framework/Android.bp b/framework/Android.bp
index c88bacc..73a1ed0 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -94,7 +94,9 @@
         "framework-wifi.stubs.module_lib",
     ],
     static_libs: [
-        "mdns_aidl_interface-lateststable-java",
+        // Not using the latest stable version because all functions in the latest version of
+        // mdns_aidl_interface are deprecated.
+        "mdns_aidl_interface-V1-java",
         "modules-utils-backgroundthread",
         "modules-utils-build",
         "modules-utils-preconditions",
@@ -301,6 +303,10 @@
     ],
     flags: [
         "--show-annotation android.annotation.FlaggedApi",
+        "--show-for-stub-purposes-annotation android.annotation.SystemApi" +
+        "\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
+        "--show-for-stub-purposes-annotation android.annotation.SystemApi" +
+        "\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
     ],
     aidl: {
         include_dirs: [
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index f959114..efae754 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -1457,6 +1457,18 @@
         return mTransportTypes == (1 << transportType);
     }
 
+    /**
+     * Returns true iff this NC has the specified transport and no other, ignoring TRANSPORT_TEST.
+     *
+     * If this NC has the passed transport and no other, this method returns true.
+     * If this NC has the passed transport, TRANSPORT_TEST and no other, this method returns true.
+     * Otherwise, this method returns false.
+     * @hide
+     */
+    public boolean hasSingleTransportBesidesTest(@Transport int transportType) {
+        return (mTransportTypes & ~(1 << TRANSPORT_TEST)) == (1 << transportType);
+    }
+
     private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
         return ((this.mTransportTypes == 0)
                 || ((this.mTransportTypes & nc.mTransportTypes) != 0));
diff --git a/service/Android.bp b/service/Android.bp
index 76741bc..e2dab9e 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -185,7 +185,7 @@
         "androidx.annotation_annotation",
         "connectivity-net-module-utils-bpf",
         "connectivity_native_aidl_interface-lateststable-java",
-        "dnsresolver_aidl_interface-V12-java",
+        "dnsresolver_aidl_interface-V13-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-ip",
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ba9ea86..8f29078 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1362,8 +1362,8 @@
         /**
          * Create a HandlerThread to use in ConnectivityService.
          */
-        public HandlerThread makeHandlerThread() {
-            return new HandlerThread("ConnectivityServiceThread");
+        public HandlerThread makeHandlerThread(@NonNull final String tag) {
+            return new HandlerThread(tag);
         }
 
         /**
@@ -1458,7 +1458,7 @@
         public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
                 @NonNull final Context context, @NonNull final TelephonyManager tm) {
             if (isAtLeastT()) {
-                return new CarrierPrivilegeAuthenticator(context, this, tm);
+                return new CarrierPrivilegeAuthenticator(context, tm);
             } else {
                 return null;
             }
@@ -1706,7 +1706,7 @@
 
         mNetd = netd;
         mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd);
-        mHandlerThread = mDeps.makeHandlerThread();
+        mHandlerThread = mDeps.makeHandlerThread("ConnectivityServiceThread");
         mPermissionMonitor =
                 new PermissionMonitor(mContext, mNetd, mBpfNetMaps, mHandlerThread);
         mHandlerThread.start();
@@ -2649,6 +2649,13 @@
         }
     }
 
+    private boolean canSeeAllowedUids(final int pid, final int uid, final int netOwnerUid) {
+        return Process.SYSTEM_UID == uid
+                || netOwnerUid == uid
+                || checkAnyPermissionOf(mContext, pid, uid,
+                        android.Manifest.permission.NETWORK_FACTORY);
+    }
+
     @VisibleForTesting
     NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
             NetworkCapabilities nc, int callerPid, int callerUid) {
@@ -2670,8 +2677,7 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)) {
             newNc.setAdministratorUids(new int[0]);
         }
-        if (!checkAnyPermissionOf(mContext,
-                callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+        if (!canSeeAllowedUids(callerPid, callerUid, newNc.getOwnerUid())) {
             newNc.setAllowedUids(new ArraySet<>());
             newNc.setSubscriptionIds(Collections.emptySet());
         }
@@ -3944,6 +3950,11 @@
         pw.println();
         dumpBpfProgramStatus(pw);
 
+        if (null != mCarrierPrivilegeAuthenticator) {
+            pw.println();
+            mCarrierPrivilegeAuthenticator.dump(pw);
+        }
+
         pw.println();
 
         if (!CollectionUtils.contains(args, SHORT_ARG)) {
@@ -5252,7 +5263,7 @@
     private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
             @NonNull final NetworkCapabilities caps) {
         if (mCarrierPrivilegeAuthenticator != null) {
-            return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+            return mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                     callingUid, caps);
         }
         return false;
diff --git a/service/src/com/android/server/NetIdManager.java b/service/src/com/android/server/NetIdManager.java
index 61925c8..27b6b9b 100644
--- a/service/src/com/android/server/NetIdManager.java
+++ b/service/src/com/android/server/NetIdManager.java
@@ -27,6 +27,16 @@
  * Class used to reserve and release net IDs.
  *
  * <p>Instances of this class are thread-safe.
+ *
+ * NetIds are currently 16 bits long and consume 16 bits in the fwmark.
+ * The reason they are large is that applications might get confused if the netId counter
+ * wraps - for example, Network#equals would return true for a current network
+ * and a long-disconnected network.
+ * We could in theory fix this by splitting the identifier in two, e.g., a 24-bit generation
+ * counter and an 8-bit netId. Java Network objects would be constructed from the full 32-bit
+ * number, but only the 8-bit number would be used by netd and the fwmark.
+ * We'd have to fix all code that assumes that it can take a netId or a mark and construct
+ * a Network object from it.
  */
 public class NetIdManager {
     // Sequence number for Networks; keep in sync with system/netd/NetworkController.cpp
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index ab7b1a7..5705ebe 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static com.android.server.connectivity.ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK;
 
@@ -31,6 +32,8 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.TransportInfo;
+import android.net.wifi.WifiInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -41,12 +44,13 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.modules.utils.HandlerExecutor;
+import com.android.net.module.util.DeviceConfigUtils;
 import com.android.networkstack.apishim.TelephonyManagerShimImpl;
 import com.android.networkstack.apishim.common.TelephonyManagerShim;
 import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
-import com.android.server.ConnectivityService;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -77,13 +81,13 @@
     private final boolean mUseCallbacksForServiceChanged;
 
     public CarrierPrivilegeAuthenticator(@NonNull final Context c,
-            @NonNull final ConnectivityService.Dependencies deps,
+            @NonNull final Dependencies deps,
             @NonNull final TelephonyManager t,
             @NonNull final TelephonyManagerShim telephonyManagerShim) {
         mContext = c;
         mTelephonyManager = t;
         mTelephonyManagerShim = telephonyManagerShim;
-        final HandlerThread thread = new HandlerThread(TAG);
+        final HandlerThread thread = deps.makeHandlerThread();
         thread.start();
         mHandler = new Handler(thread.getLooper());
         mUseCallbacksForServiceChanged = deps.isFeatureEnabled(
@@ -109,9 +113,24 @@
     }
 
     public CarrierPrivilegeAuthenticator(@NonNull final Context c,
-            @NonNull final ConnectivityService.Dependencies deps,
             @NonNull final TelephonyManager t) {
-        this(c, deps, t, TelephonyManagerShimImpl.newInstance(t));
+        this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t));
+    }
+
+    public static class Dependencies {
+        /**
+         * Create a HandlerThread to use in CarrierPrivilegeAuthenticator.
+         */
+        public HandlerThread makeHandlerThread() {
+            return new HandlerThread(TAG);
+        }
+
+        /**
+         * @see DeviceConfigUtils#isTetheringFeatureEnabled
+         */
+        public boolean isFeatureEnabled(Context context, String name) {
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, name);
+        }
     }
 
     private void simConfigChanged() {
@@ -125,11 +144,13 @@
 
     private class PrivilegeListener implements CarrierPrivilegesListenerShim {
         public final int mLogicalSlot;
+
         PrivilegeListener(final int logicalSlot) {
             mLogicalSlot = logicalSlot;
         }
 
-        @Override public void onCarrierPrivilegesChanged(
+        @Override
+        public void onCarrierPrivilegesChanged(
                 @NonNull List<String> privilegedPackageNames,
                 @NonNull int[] privilegedUids) {
             if (mUseCallbacksForServiceChanged) return;
@@ -193,12 +214,13 @@
      *
      * This returns whether the passed UID is the carrier service package for the subscription ID
      * stored in the telephony network specifier in the passed network capabilities.
-     * If the capabilities don't code for a cellular network, or if they don't have the
+     * If the capabilities don't code for a cellular or Wi-Fi network, or if they don't have the
      * subscription ID in their specifier, this returns false.
      *
-     * This method can be used to check that a network request for {@link NET_CAPABILITY_CBS} is
-     * allowed for the UID of a caller, which must hold carrier privilege and provide the carrier
-     * config.
+     * This method can be used to check that a network request that requires the UID to be
+     * the carrier service UID is indeed called by such a UID. An example of such a network could
+     * be a network with the  {@link android.net.NetworkCapabilities#NET_CAPABILITY_CBS}
+     * capability.
      * It can also be used to check that a factory is entitled to grant access to a given network
      * to a given UID on grounds that it is the carrier service package.
      *
@@ -206,11 +228,28 @@
      * @param networkCapabilities the network capabilities for which carrier privilege is checked.
      * @return true if uid provides the relevant carrier config else false.
      */
-    public boolean hasCarrierPrivilegeForNetworkCapabilities(int callingUid,
+    public boolean isCarrierServiceUidForNetworkCapabilities(int callingUid,
             @NonNull NetworkCapabilities networkCapabilities) {
         if (callingUid == Process.INVALID_UID) return false;
-        if (!networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)) return false;
-        final int subId = getSubIdFromNetworkSpecifier(networkCapabilities.getNetworkSpecifier());
+        final int subId;
+        if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)) {
+            subId = getSubIdFromTelephonySpecifier(networkCapabilities.getNetworkSpecifier());
+        } else if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_WIFI)) {
+            subId = getSubIdFromWifiTransportInfo(networkCapabilities.getTransportInfo());
+        } else {
+            subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && !networkCapabilities.getSubscriptionIds().contains(subId)) {
+            // Ideally, the code above should just use networkCapabilities.getSubscriptionIds()
+            // for simplicity and future-proofing. However, this is not the historical behavior,
+            // and there is no enforcement that they do not differ, so log a terrible failure if
+            // they do not match to gain confidence this never happens.
+            // TODO : when there is confidence that this never happens, rewrite the code above
+            // with NetworkCapabilities#getSubscriptionIds.
+            Log.wtf(TAG, "NetworkCapabilities subIds are inconsistent between "
+                    + "specifier/transportInfo and mSubIds : " + networkCapabilities);
+        }
         if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
         return callingUid == getCarrierServiceUidForSubId(subId);
     }
@@ -239,14 +278,6 @@
     }
 
     @VisibleForTesting
-    int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
-        if (specifier instanceof TelephonyNetworkSpecifier) {
-            return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
-        }
-        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    }
-
-    @VisibleForTesting
     int getUidForPackage(String pkgName) {
         if (pkgName == null) {
             return Process.INVALID_UID;
@@ -271,8 +302,22 @@
         return getUidForPackage(getCarrierServicePackageNameForLogicalSlot(slotId));
     }
 
-    // Helper methods to avoid having to deal with UnsupportedApiLevelException.
+    @VisibleForTesting
+    int getSubIdFromTelephonySpecifier(@Nullable final NetworkSpecifier specifier) {
+        if (specifier instanceof TelephonyNetworkSpecifier) {
+            return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
 
+    int getSubIdFromWifiTransportInfo(@Nullable final TransportInfo info) {
+        if (info instanceof WifiInfo) {
+            return ((WifiInfo) info).getSubscriptionId();
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    // Helper methods to avoid having to deal with UnsupportedApiLevelException.
     private void addCarrierPrivilegesListener(@NonNull final Executor executor,
             @NonNull final PrivilegeListener listener) {
         try {
@@ -292,4 +337,16 @@
             Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
         }
     }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("CarrierPrivilegeAuthenticator:");
+        synchronized (mLock) {
+            final int size = mCarrierServiceUid.size();
+            for (int i = 0; i < size; ++i) {
+                final int logicalSlot = mCarrierServiceUid.keyAt(i);
+                final int serviceUid = mCarrierServiceUid.valueAt(i);
+                pw.println("Logical slot = " + logicalSlot + " : uid = " + serviceUid);
+            }
+        }
+    }
 }
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 5aac8f1..f8f76ef 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -33,7 +33,6 @@
     public static final String NO_REMATCH_ALL_REQUESTS_ON_REGISTER =
             "no_rematch_all_requests_on_register";
 
-    @VisibleForTesting
     public static final String CARRIER_SERVICE_CHANGED_USE_CALLBACK =
             "carrier_service_changed_use_callback_version";
 
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 7cd3cc8..50cad45 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,11 +17,13 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+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_RESTRICTED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -1549,7 +1551,7 @@
      * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
      * @param authenticator the carrier privilege authenticator to check for telephony constraints
      */
-    public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+    public void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
             final int creatorUid, final boolean hasAutomotiveFeature,
             @NonNull final ConnectivityService.Dependencies deps,
             @Nullable final CarrierPrivilegeAuthenticator authenticator) {
@@ -1562,7 +1564,7 @@
         }
     }
 
-    private static boolean areAllowedUidsAcceptableFromNetworkAgent(
+    private boolean areAllowedUidsAcceptableFromNetworkAgent(
             @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
             @NonNull final ConnectivityService.Dependencies deps,
             @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
@@ -1575,21 +1577,28 @@
         // On a non-restricted network, access UIDs make no sense
         if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
 
-        // If this network has TRANSPORT_TEST, then the caller can do whatever they want to
-        // access UIDs
-        if (nc.hasTransport(TRANSPORT_TEST)) return true;
+        // If this network has TRANSPORT_TEST and nothing else, then the caller can do whatever
+        // they want to access UIDs
+        if (nc.hasSingleTransport(TRANSPORT_TEST)) return true;
 
-        // Factories that make ethernet networks can allow UIDs for automotive devices.
-        if (nc.hasSingleTransport(TRANSPORT_ETHERNET) && hasAutomotiveFeature) {
-            return true;
+        if (nc.hasTransport(TRANSPORT_ETHERNET)) {
+            // Factories that make ethernet networks can allow UIDs for automotive devices.
+            if (hasAutomotiveFeature) return true;
+            // It's also admissible if the ethernet network has TRANSPORT_TEST, as long as it
+            // doesn't have NET_CAPABILITY_INTERNET so it can't become the default network.
+            if (nc.hasTransport(TRANSPORT_TEST) && !nc.hasCapability(NET_CAPABILITY_INTERNET)) {
+                return true;
+            }
+            return false;
         }
 
-        // Factories that make cell networks can allow the UID for the carrier service package.
+        // Factories that make cell/wifi networks can allow the UID for the carrier service package.
         // This can only work in T where there is support for CarrierPrivilegeAuthenticator
         if (null != carrierPrivilegeAuthenticator
-                && nc.hasSingleTransport(TRANSPORT_CELLULAR)
+                && (nc.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)
+                        || nc.hasSingleTransportBesidesTest(TRANSPORT_WIFI))
                 && (1 == nc.getAllowedUidsNoCopy().size())
-                && (carrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                && (carrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                         nc.getAllowedUidsNoCopy().valueAt(0), nc))) {
             return true;
         }
diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
index e4de812..e81fb92 100644
--- a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
+++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
@@ -74,11 +74,27 @@
     ASSERT_RESULT_OK(result);
     EXPECT_TRUE(result.value()->isEmpty());
 
+    struct timespec t1, t2;
+    EXPECT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &t1));
+    EXPECT_FALSE(result.value()->wait(1000 /*ms*/));  // false because wait should timeout
+    EXPECT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &t2));
+    long long time1 = t1.tv_sec * 1000000000LL + t1.tv_nsec;
+    long long time2 = t2.tv_sec * 1000000000LL + t2.tv_nsec;
+    EXPECT_GE(time2 - time1, 1000000000 /*ns*/);  // 1000 ms as ns
+
     for (int i = 0; i < n; i++) {
       RunProgram();
     }
 
     EXPECT_FALSE(result.value()->isEmpty());
+
+    EXPECT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &t1));
+    EXPECT_TRUE(result.value()->wait());
+    EXPECT_EQ(0, clock_gettime(CLOCK_MONOTONIC, &t2));
+    time1 = t1.tv_sec * 1000000000LL + t1.tv_nsec;
+    time2 = t2.tv_sec * 1000000000LL + t2.tv_nsec;
+    EXPECT_LE(time2 - time1, 1000000 /*ns*/);  // in x86 CF testing < 5000 ns
+
     EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(n));
     EXPECT_TRUE(result.value()->isEmpty());
     EXPECT_EQ(output, TEST_RINGBUF_MAGIC_NUM);
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index 9aff790..d716358 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -19,6 +19,7 @@
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
 #include <linux/bpf.h>
+#include <poll.h>
 #include <sys/mman.h>
 #include <utils/Log.h>
 
@@ -41,6 +42,9 @@
 
   bool isEmpty(void);
 
+  // returns !isEmpty() for convenience
+  bool wait(int timeout_ms = -1);
+
  protected:
   // Non-initializing constructor, used by Create.
   BpfRingbufBase(size_t value_size) : mValueSize(value_size) {}
@@ -200,12 +204,21 @@
 }
 
 inline bool BpfRingbufBase::isEmpty(void) {
-  uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire);
-  // Only userspace writes to mConsumerPos, so no need to use std::memory_order_acquire
+  uint32_t prod_pos = mProducerPos->load(std::memory_order_relaxed);
   uint64_t cons_pos = mConsumerPos->load(std::memory_order_relaxed);
   return (cons_pos & 0xFFFFFFFF) == prod_pos;
 }
 
+inline bool BpfRingbufBase::wait(int timeout_ms) {
+  // possible optimization: if (!isEmpty()) return true;
+  struct pollfd pfd = {  // 1-element array
+    .fd = mRingFd.get(),
+    .events = POLLIN,
+  };
+  (void)poll(&pfd, 1, timeout_ms);  // 'best effort' poll
+  return !isEmpty();
+}
+
 inline base::Result<int> BpfRingbufBase::ConsumeAll(
     const std::function<void(const void*)>& callback) {
   int64_t count = 0;
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 56565ed..637a938 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -225,19 +225,6 @@
 
 }
 
-java_library {
-    name: "mdns_aidl_interface-lateststable-java",
-    sdk_version: "module_current",
-    min_sdk_version: "30",
-    static_libs: [
-        "mdns_aidl_interface-V1-java",
-    ],
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
-    ],
-}
-
 aidl_interface {
     name: "mdns_aidl_interface",
     local_include_dir: "binder",
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index d75d9ca..df6067d 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -61,8 +61,8 @@
                     commonError)
         }
         assertTrue(tm.isDataConnectivityPossible,
-            "The device is not setup with a SIM card that supports data connectivity. " +
-                    commonError)
+            "The device has a SIM card, but it does not supports data connectivity. " +
+            "Check the data plan, and verify that mobile data is working. " + commonError)
         connectUtil.ensureCellularValidated()
     }
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 1ba83ca..10accd4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -17,9 +17,9 @@
 package com.android.testutils
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import java.lang.IllegalStateException
 import java.lang.reflect.Modifier
 import org.junit.runner.Description
 import org.junit.runner.Runner
@@ -110,10 +110,19 @@
 
         notifier.fireTestStarted(leakMonitorDesc)
         val threadCountsAfterTest = getAllThreadNameCounts()
-        if (threadCountsBeforeTest != threadCountsAfterTest) {
+        // TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
+        val threadsDiff = CompareOrUpdateResult(
+                threadCountsBeforeTest.entries,
+                threadCountsAfterTest.entries
+        ) { it.key }
+        // Ignore removed threads, which typically are generated by previous tests.
+        // Because this is in the threadsDiff.updated member, for sure there is a
+        // corresponding key in threadCountsBeforeTest.
+        val increasedThreads = threadsDiff.updated
+                .filter { threadCountsBeforeTest[it.key]!! < it.value }
+        if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
             notifier.fireTestFailure(Failure(leakMonitorDesc,
-                    IllegalStateException("Expected threads: $threadCountsBeforeTest " +
-                            "but got: $threadCountsAfterTest")))
+                    IllegalStateException("Unexpected thread changes: $threadsDiff")))
         }
         notifier.fireTestFinished(leakMonitorDesc)
     }
@@ -121,9 +130,13 @@
     private fun getAllThreadNameCounts(): Map<String, Int> {
         // Get the counts of threads in the group per name.
         // Filter system thread groups.
+        // Also ignore threads with 1 count, this effectively filtered out threads created by the
+        // test runner or other system components. e.g. hwuiTask*, queued-work-looper,
+        // SurfaceSyncGroupTimer, RenderThread, Time-limited test, etc.
         return Thread.getAllStackTraces().keys
                 .filter { it.threadGroup?.name != "system" }
                 .groupingBy { it.name }.eachCount()
+                .filter { it.value != 1 }
     }
 
     override fun getDescription(): Description {
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index b86de25..9310888 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -62,10 +62,13 @@
     // uncomment when b/13249961 is fixed
     // sdk_version: "current",
     platform_apis: true,
-    data: [":ConnectivityTestPreparer"],
     per_testcase_directory: true,
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate.xml",
+    data: [
+        ":ConnectivityTestPreparer",
+        ":CtsCarrierServicePackage",
+    ]
 }
 
 // Networking CTS tests for development and release. These tests always target the platform SDK
@@ -154,3 +157,17 @@
     package_name: "android.net.cts.maxtargetsdk30", // CTS package names must be unique.
     instrumentation_target_package: "android.net.cts.maxtargetsdk30",
 }
+
+android_test_helper_app {
+    name: "CtsCarrierServicePackage",
+    defaults: ["cts_defaults"],
+    package_name: "android.net.cts.carrierservicepackage",
+    manifest: "carrierservicepackage/AndroidManifest.xml",
+    srcs: ["carrierservicepackage/src/**/*.java"],
+    min_sdk_version: "30",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 68e36ff..098cc0a 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -36,6 +36,7 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <!-- This test also uses signature permissions through adopting the shell identity.
          The permissions acquired that way include (probably not exhaustive) :
@@ -46,6 +47,7 @@
                  android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
+
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
@@ -54,4 +56,3 @@
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 8efa99f..38f26d8 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -27,6 +27,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="{MODULE}.apk" />
+        <option name="test-file-name" value="CtsCarrierServicePackage.apk" />
     </target_preparer>
     <target_preparer class="com.android.testutils.ConnectivityTestTargetPreparer">
     </target_preparer>
diff --git a/tests/cts/net/carrierservicepackage/AndroidManifest.xml b/tests/cts/net/carrierservicepackage/AndroidManifest.xml
new file mode 100644
index 0000000..c2a45eb
--- /dev/null
+++ b/tests/cts/net/carrierservicepackage/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     android:versionCode="1"
+     android:versionName="1.0.0"
+     package="android.net.cts.carrierservicepackage">
+    <uses-sdk android:minSdkVersion="30"
+              android:targetSdkVersion="33" />
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+    <application android:allowBackup="false"
+         android:directBootAware="true">
+        <service android:name=".DummyCarrierConfigService"
+                 android:permission="android.permission.BIND_CARRIER_SERVICES"
+                 android:exported="true">
+            <intent-filter>
+              <action android:name="android.service.carrier.CarrierService"/>
+            </intent-filter>
+        </service>
+    </application>
+
+</manifest>
diff --git a/tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java b/tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java
new file mode 100644
index 0000000..ca2015b
--- /dev/null
+++ b/tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.carrierservicepackage;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.service.carrier.CarrierIdentifier;
+import android.service.carrier.CarrierService;
+
+public class DummyCarrierConfigService extends CarrierService {
+    private static final String TAG = "DummyCarrierConfigService";
+
+    public DummyCarrierConfigService() {}
+
+    @Override
+    public PersistableBundle onLoadConfig(CarrierIdentifier id) {
+        return new PersistableBundle(); // Do nothing
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return super.onBind(intent);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        return super.onUnbind(intent);
+    }
+}
+
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 0a143c5..778f0c5 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -796,14 +796,15 @@
             // Make sure that the NC is null if the package doesn't hold ACCESS_NETWORK_STATE.
             assertNull(redactNc(nc, groundedUid, groundedPkg));
 
-            // Uids, ssid, underlying networks & subscriptionIds will be redacted if the given uid
+            // Uids, ssid & underlying networks will be redacted if the given uid
             // doesn't hold the associated permissions. The wifi transport info is also suitably
             // redacted.
             final NetworkCapabilities redactedNormal = redactNc(nc, normalUid, normalPkg);
             assertNull(redactedNormal.getUids());
             assertNull(redactedNormal.getSsid());
             assertNull(redactedNormal.getUnderlyingNetworks());
-            assertEquals(0, redactedNormal.getSubscriptionIds().size());
+            // Owner UID is allowed to see the subscription IDs.
+            assertEquals(2, redactedNormal.getSubscriptionIds().size());
             assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS,
                     ((WifiInfo) redactedNormal.getTransportInfo()).getBSSID());
             assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi());
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 392cba9..225408c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -15,9 +15,12 @@
  */
 package android.net.cts
 
+import android.Manifest.permission.MODIFY_PHONE_STATE
 import android.Manifest.permission.NETWORK_SETTINGS
+import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
 import android.app.Instrumentation
 import android.content.Context
+import android.content.pm.PackageManager
 import android.net.ConnectivityManager
 import android.net.EthernetNetworkSpecifier
 import android.net.INetworkAgent
@@ -44,7 +47,9 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
@@ -53,6 +58,7 @@
 import android.net.NetworkReleasedException
 import android.net.NetworkRequest
 import android.net.NetworkScore
+import android.net.NetworkSpecifier
 import android.net.QosCallback
 import android.net.QosCallback.QosCallbackRegistrationException
 import android.net.QosCallbackException
@@ -61,6 +67,7 @@
 import android.net.QosSocketInfo
 import android.net.RouteInfo
 import android.net.SocketKeepalive
+import android.net.TelephonyNetworkSpecifier
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
 import android.net.Uri
@@ -69,21 +76,31 @@
 import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnError
 import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionAvailable
 import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
+import android.net.wifi.WifiInfo
 import android.os.Build
+import android.os.ConditionVariable
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Message
+import android.os.PersistableBundle
 import android.os.Process
 import android.os.SystemClock
 import android.platform.test.annotations.AppModeFull
 import android.system.OsConstants.IPPROTO_TCP
 import android.system.OsConstants.IPPROTO_UDP
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback
 import android.telephony.data.EpsBearerQosSessionAttributes
+import android.util.ArraySet
 import android.util.DebugUtils.valueToString
+import android.util.Log
 import androidx.test.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
+import com.android.compatibility.common.util.UiccUtil
 import com.android.modules.utils.build.SdkLevel
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.testutils.CompatUtil
@@ -112,23 +129,8 @@
 import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.assertThrows
-import java.io.Closeable
-import java.io.IOException
-import java.net.DatagramSocket
-import java.net.InetAddress
-import java.net.InetSocketAddress
-import java.net.Socket
-import java.time.Duration
-import java.util.Arrays
-import java.util.UUID
-import java.util.concurrent.Executors
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
+import com.android.testutils.runAsShell
+import com.android.testutils.tryTest
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -140,7 +142,26 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.timeout
 import org.mockito.Mockito.verify
+import java.io.Closeable
+import java.io.IOException
+import java.net.DatagramSocket
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.Socket
+import java.security.MessageDigest
+import java.time.Duration
+import java.util.Arrays
+import java.util.UUID
+import java.util.concurrent.Executors
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
 
+private const val TAG = "NetworkAgentTest"
 // This test doesn't really have a constraint on how fast the methods should return. If it's
 // going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
 // without affecting the run time of successful runs. Thus, set a very high timeout.
@@ -261,17 +282,18 @@
         callbacksToCleanUp.add(callback)
     }
 
-    private fun makeTestNetworkRequest(specifier: String? = null): NetworkRequest {
-        return NetworkRequest.Builder()
-                .clearCapabilities()
-                .addTransportType(TRANSPORT_TEST)
-                .also {
-                    if (specifier != null) {
-                        it.setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(specifier))
-                    }
-                }
-                .build()
-    }
+    private fun String?.asEthSpecifier(): NetworkSpecifier? =
+            if (null == this) null else CompatUtil.makeEthernetNetworkSpecifier(this)
+    private fun makeTestNetworkRequest(specifier: NetworkSpecifier? = null) =
+            NetworkRequest.Builder().run {
+                clearCapabilities()
+                addTransportType(TRANSPORT_TEST)
+                if (specifier != null) setNetworkSpecifier(specifier)
+                build()
+            }
+
+    private fun makeTestNetworkRequest(specifier: String?) =
+            makeTestNetworkRequest(specifier.asEthSpecifier())
 
     private fun makeTestNetworkCapabilities(
         specifier: String? = null,
@@ -322,7 +344,7 @@
     ): Pair<TestableNetworkAgent, TestableNetworkCallback> {
         val callback = TestableNetworkCallback()
         // Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
-        requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
+        requestNetwork(makeTestNetworkRequest(specifier), callback)
         val nc = makeTestNetworkCapabilities(specifier, transports)
         val agent = createNetworkAgent(context, initialConfig = initialConfig, initialNc = nc)
         agent.setTeardownDelayMillis(0)
@@ -543,6 +565,231 @@
                 .addTransportType(TRANSPORT_TEST)
                 .setAllowedUids(uids.toSet()).build()
 
+    /**
+     * Get the single element from this ArraySet, or fail() if doesn't contain exactly 1 element.
+     */
+    fun <T> ArraySet<T>.getSingleElement(): T {
+        if (size != 1) fail("Expected exactly one element, contained $size")
+        return iterator().next()
+    }
+
+    private fun doTestAllowedUids(
+            subId: Int,
+            transport: Int,
+            uid: Int,
+            expectUidsPresent: Boolean
+    ) {
+        doTestAllowedUids(subId, intArrayOf(transport), uid, expectUidsPresent)
+    }
+
+    private fun doTestAllowedUids(
+            subId: Int,
+            transports: IntArray,
+            uid: Int,
+            expectUidsPresent: Boolean
+    ) {
+        val callback = TestableNetworkCallback(DEFAULT_TIMEOUT_MS)
+        val specifier = when {
+            transports.size != 1 -> null
+            TRANSPORT_ETHERNET in transports -> EthernetNetworkSpecifier("testInterface")
+            TRANSPORT_CELLULAR in transports -> TelephonyNetworkSpecifier(subId)
+            else -> null
+        }
+        val agent = createNetworkAgent(initialNc = NetworkCapabilities.Builder().run {
+            addTransportType(TRANSPORT_TEST)
+            transports.forEach { addTransportType(it) }
+            addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+            addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+            removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+            setNetworkSpecifier(specifier)
+            if (TRANSPORT_WIFI in transports && SdkLevel.isAtLeastV()) {
+                // setSubscriptionId only exists in V+
+                setTransportInfo(WifiInfo.Builder().setSubscriptionId(subId).build())
+            }
+            setAllowedUids(setOf(uid))
+            setOwnerUid(Process.myUid())
+            setAdministratorUids(intArrayOf(Process.myUid()))
+            build()
+        })
+        runWithShellPermissionIdentity {
+            agent.register()
+        }
+        agent.markConnected()
+
+        registerNetworkCallback(makeTestNetworkRequest(specifier), callback)
+        callback.expect<Available>(agent.network!!)
+        callback.expect<CapabilitiesChanged>(agent.network!!) {
+            if (expectUidsPresent) {
+                it.caps.allowedUidsNoCopy.getSingleElement() == uid
+            } else {
+                it.caps.allowedUidsNoCopy.isEmpty()
+            }
+        }
+        agent.unregister()
+        // callback will be unregistered in tearDown()
+    }
+
+    private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
+        fun getCertHash(): String {
+            val pkgInfo = realContext.packageManager.getPackageInfo(realContext.opPackageName,
+                    PackageManager.GET_SIGNATURES)
+            val digest = MessageDigest.getInstance("SHA-256")
+            val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
+            return UiccUtil.bytesToHexString(certHash)!!
+        }
+
+        val tm = realContext.getSystemService(TelephonyManager::class.java)!!
+        val ccm = realContext.getSystemService(CarrierConfigManager::class.java)!!
+
+        val cv = ConditionVariable()
+        val cpb = PrivilegeWaiterCallback(cv)
+        tryTest {
+            val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) {
+                tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+            }
+            // Wait for the callback to be registered
+            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
+            if (cpb.hasPrivilege == hold) {
+                if (hold) {
+                    Log.w(TAG, "Package ${realContext.opPackageName} already is privileged")
+                } else {
+                    Log.w(TAG, "Package ${realContext.opPackageName} already isn't privileged")
+                }
+                return@tryTest
+            }
+            cv.close()
+            runAsShell(MODIFY_PHONE_STATE) {
+                val carrierConfigs = if (hold) {
+                    PersistableBundle().also {
+                        it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+                                arrayOf(getCertHash()))
+                    }
+                } else {
+                    null
+                }
+                ccm.overrideConfig(subId, carrierConfigs)
+            }
+            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't change carrier privilege")
+        } cleanup {
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) {
+                tm.unregisterCarrierPrivilegesCallback(cpb)
+            }
+        }
+    }
+
+    private fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
+    private fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
+
+    private fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
+        val tm = realContext.getSystemService(TelephonyManager::class.java)!!
+
+        val cv = ConditionVariable()
+        val cpb = CarrierServiceChangedWaiterCallback(cv)
+        tryTest {
+            val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) {
+                tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+            }
+            // Wait for the callback to be registered
+            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
+            if (cpb.pkgName == pkg) {
+                Log.w(TAG, "Carrier service package was already $pkg")
+                return@tryTest
+            }
+            cv.close()
+            runAsShell(MODIFY_PHONE_STATE) {
+                if (null == pkg) {
+                    // There is a bug is clear-carrier-service-package-override where not adding
+                    // the -s argument will use the wrong slot index : b/299604822
+                    runShellCommand("cmd phone clear-carrier-service-package-override" +
+                            " -s $subId")
+                } else {
+                    // -s could set the subId, but this test works with the default subId.
+                    runShellCommand("cmd phone set-carrier-service-package-override $pkg")
+                }
+            }
+            assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't modify carrier service package")
+        } cleanup {
+            runAsShell(READ_PRIVILEGED_PHONE_STATE) {
+                tm.unregisterCarrierPrivilegesCallback(cpb)
+            }
+        }
+    }
+
+    private fun String.execute() = runShellCommand(this).trim()
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S)
+    fun testAllowedUids() {
+        // Use a different package than this one to make sure that a package that doesn't hold
+        // carrier service permission can be set as an allowed UID.
+        val servicePackage = "android.net.cts.carrierservicepackage"
+        val uid = try {
+            realContext.packageManager.getApplicationInfo(servicePackage, 0).uid
+        } catch (e: PackageManager.NameNotFoundException) {
+            fail("$servicePackage could not be installed, please check the SuiteApkInstaller" +
+                    " installed CtsCarrierServicePackage.apk", e)
+        }
+
+        val tm = realContext.getSystemService(TelephonyManager::class.java)!!
+        val defaultSubId = SubscriptionManager.getDefaultSubscriptionId()
+        tryTest {
+            // This process is not the carrier service UID, so allowedUids should be ignored in all
+            // the following cases.
+            doTestAllowedUids(defaultSubId, TRANSPORT_CELLULAR, uid, expectUidsPresent = false)
+            doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = false)
+            doTestAllowedUids(defaultSubId, TRANSPORT_BLUETOOTH, uid, expectUidsPresent = false)
+
+            // The tools to set the carrier service package override do not exist before U,
+            // so there is no way to test the rest of this test on < U.
+            if (!SdkLevel.isAtLeastU()) return@tryTest
+            // Acquiring carrier privilege is necessary to override the carrier service package.
+            val defaultSlotIndex = SubscriptionManager.getSlotIndex(defaultSubId)
+            acquireCarrierPrivilege(defaultSubId)
+            setCarrierServicePackageOverride(defaultSubId, servicePackage)
+            val actualServicePackage: String? = runAsShell(READ_PRIVILEGED_PHONE_STATE) {
+                tm.getCarrierServicePackageNameForLogicalSlot(defaultSlotIndex)
+            }
+            assertEquals(servicePackage, actualServicePackage)
+
+            // Wait for CarrierServiceAuthenticator to have seen the update of the service package
+            val timeout = SystemClock.elapsedRealtime() + DEFAULT_TIMEOUT_MS
+            while (true) {
+                if (SystemClock.elapsedRealtime() > timeout) {
+                    fail("Couldn't make $servicePackage the service package for $defaultSubId: " +
+                            "dumpsys connectivity".execute().split("\n")
+                                    .filter { it.contains("Logical slot = $defaultSlotIndex.*") })
+                }
+                if ("dumpsys connectivity"
+                        .execute()
+                        .split("\n")
+                        .filter { it.contains("Logical slot = $defaultSlotIndex : uid = $uid") }
+                        .isNotEmpty()) {
+                    // Found the configuration
+                    break
+                }
+                Thread.sleep(500)
+            }
+
+            // Cell and WiFi are allowed to set UIDs, but not Bluetooth or agents with multiple
+            // transports.
+            doTestAllowedUids(defaultSubId, TRANSPORT_CELLULAR, uid, expectUidsPresent = true)
+            if (SdkLevel.isAtLeastV()) {
+                // Cannot be tested before V because WifiInfo.Builder#setSubscriptionId doesn't
+                // exist
+                doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = true)
+            }
+            doTestAllowedUids(defaultSubId, TRANSPORT_BLUETOOTH, uid, expectUidsPresent = false)
+            doTestAllowedUids(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI), uid,
+                    expectUidsPresent = false)
+        } cleanupStep {
+            if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null)
+        } cleanup {
+            if (SdkLevel.isAtLeastU()) dropCarrierPrivilege(defaultSubId)
+        }
+    }
+
     @Test
     fun testRejectedUpdates() {
         val callback = TestableNetworkCallback(DEFAULT_TIMEOUT_MS)
@@ -1497,3 +1744,25 @@
         doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
     }
 }
+
+// Subclasses of CarrierPrivilegesCallback can't be inline, or they'll be compiled as
+// inner classes of the test class and will fail resolution on R as the test harness
+// uses reflection to list all methods and classes
+class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
+        CarrierPrivilegesCallback {
+    var hasPrivilege = false
+    override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
+        hasPrivilege = uids.contains(Process.myUid())
+        cv.open()
+    }
+}
+
+class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
+        CarrierPrivilegesCallback {
+    var pkgName: String? = null
+    override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
+    override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
+        this.pkgName = pkgName
+        cv.open()
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 9c44a3e..e1ea2b9 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -80,6 +80,7 @@
 import com.android.net.module.util.PacketBuilder
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
@@ -843,6 +844,8 @@
         checkConnectSocketToMdnsd(shouldFail = false)
     }
 
+    // Native mdns powered by Netd is removed after U.
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @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
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b8cf08e..9fad766 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -79,7 +79,6 @@
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
-import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
@@ -1659,8 +1658,7 @@
             waitForIdle();
         }
 
-        public void startLegacyVpnPrivileged(VpnProfile profile,
-                @Nullable Network underlying, @NonNull LinkProperties egress) {
+        public void startLegacyVpnPrivileged(VpnProfile profile) {
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -1992,7 +1990,7 @@
         }
 
         @Override
-        public HandlerThread makeHandlerThread() {
+        public HandlerThread makeHandlerThread(@NonNull final String tag) {
             return mCsHandlerThread;
         }
 
@@ -10252,7 +10250,7 @@
         b.expectBroadcast();
         // Simulate LockdownVpnTracker attempting to start the VPN since it received the
         // systemDefault callback.
-        mMockVpn.startLegacyVpnPrivileged(profile, mCellAgent.getNetwork(), cellLp);
+        mMockVpn.startLegacyVpnPrivileged(profile);
         if (expectSetVpnDefaultForUids) {
             // setVpnDefaultForUids() releases the original network request and creates a VPN
             // request so LOST callback is received.
@@ -10323,7 +10321,7 @@
         // callback with different network.
         final ExpectedBroadcast b6 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
         mMockVpn.stopVpnRunnerPrivileged();
-        mMockVpn.startLegacyVpnPrivileged(profile, mWiFiAgent.getNetwork(), wifiLp);
+        mMockVpn.startLegacyVpnPrivileged(profile);
         // VPN network is disconnected (to restart)
         callback.expect(LOST, mMockVpn);
         defaultCallback.expect(LOST, mMockVpn);
@@ -17386,6 +17384,7 @@
         mCm.requestNetwork(new NetworkRequest.Builder()
                         .clearCapabilities()
                         .addTransportType(TRANSPORT_TEST)
+                        .addTransportType(TRANSPORT_CELLULAR)
                         .build(),
                 cb);
 
@@ -17531,7 +17530,7 @@
 
         // In this test TEST_PACKAGE_UID will be the UID of the carrier service UID.
         doReturn(true).when(mCarrierPrivilegeAuthenticator)
-                .hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+                .isCarrierServiceUidForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
 
         // Simulate a restricted telephony network. The telephony factory is entitled to set
         // the access UID to the service package on any of its restricted networks.
@@ -17596,17 +17595,18 @@
         // TODO : fix the builder
         ncb.setNetworkSpecifier(null);
         ncb.removeTransportType(TRANSPORT_CELLULAR);
-        ncb.addTransportType(TRANSPORT_WIFI);
+        ncb.addTransportType(TRANSPORT_BLUETOOTH);
         // Wifi does not get to set access UID, even to the correct UID
         mCm.requestNetwork(new NetworkRequest.Builder()
-                .addTransportType(TRANSPORT_WIFI)
+                .addTransportType(TRANSPORT_BLUETOOTH)
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                 .build(), cb);
-        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), ncb.build());
-        mWiFiAgent.connect(true);
-        cb.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        final TestNetworkAgentWrapper bluetoothAgent = new TestNetworkAgentWrapper(
+                TRANSPORT_BLUETOOTH, new LinkProperties(), ncb.build());
+        bluetoothAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(bluetoothAgent);
         ncb.setAllowedUids(serviceUidSet);
-        mWiFiAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        bluetoothAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
         cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
         mCm.unregisterNetworkCallback(cb);
     }
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index 8113626..f07593e 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -45,6 +45,7 @@
 import android.net.NetworkCapabilities;
 import android.net.TelephonyNetworkSpecifier;
 import android.os.Build;
+import android.os.HandlerThread;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
@@ -52,10 +53,11 @@
 import com.android.networkstack.apishim.TelephonyManagerShimImpl;
 import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
-import com.android.server.ConnectivityService;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator.Dependencies;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
 
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -86,10 +88,11 @@
     private final int mCarrierConfigPkgUid = 12345;
     private final String mTestPkg = "com.android.server.connectivity.test";
     private final BroadcastReceiver mMultiSimBroadcastReceiver;
+    @NonNull private final HandlerThread mHandlerThread;
 
     public class TestCarrierPrivilegeAuthenticator extends CarrierPrivilegeAuthenticator {
         TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
-                @NonNull final ConnectivityService.Dependencies deps,
+                @NonNull final Dependencies deps,
                 @NonNull final TelephonyManager t) {
             super(c, deps, t, mTelephonyManagerShim);
         }
@@ -100,6 +103,11 @@
         }
     }
 
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
+
     /** Parameters to test both using callbacks or the old broadcast */
     @Parameterized.Parameters
     public static Collection<Boolean> shouldUseCallbacks() {
@@ -111,9 +119,11 @@
         mTelephonyManager = mock(TelephonyManager.class);
         mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
         mPackageManager = mock(PackageManager.class);
-        final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class);
+        mHandlerThread = new HandlerThread(CarrierPrivilegeAuthenticatorTest.class.getSimpleName());
+        final Dependencies deps = mock(Dependencies.class);
         doReturn(useCallbacks).when(deps).isFeatureEnabled(any() /* context */,
                 eq(CARRIER_SERVICE_CHANGED_USE_CALLBACK));
+        doReturn(mHandlerThread).when(deps).makeHandlerThread();
         doReturn(SUBSCRIPTION_COUNT).when(mTelephonyManager).getActiveModemCount();
         doReturn(mTestPkg).when(mTelephonyManagerShim)
                 .getCarrierServicePackageNameForLogicalSlot(anyInt());
@@ -164,9 +174,9 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
 
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid + 1, ncBuilder.build()));
     }
 
@@ -203,9 +213,9 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(specifier)
                 .build();
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, nc));
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid + 1, nc));
     }
 
@@ -225,9 +235,9 @@
         listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
         listener.onCarrierServiceChanged(null, applicationInfo.uid);
 
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, nc));
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid + 1, nc));
     }
 
@@ -238,11 +248,11 @@
 
         final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
         ncBuilder.addTransportType(TRANSPORT_CELLULAR);
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
 
         ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
 
         // The builder for NetworkCapabilities doesn't allow removing the transport as long as a
@@ -251,7 +261,7 @@
         ncBuilder.removeTransportType(TRANSPORT_CELLULAR);
         ncBuilder.addTransportType(TRANSPORT_WIFI);
         ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+        assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
                 mCarrierConfigPkgUid, ncBuilder.build()));
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 545ed16..afb9abd 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -140,7 +140,8 @@
         assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
         assertContainsExactly(actual.transportTypes, expected.transportTypes);
         assertEquals(actual.meteredNetwork, expected.meteredNetwork);
-        assertFieldCountEquals(17, ResolverParamsParcel.class);
+        assertEquals(actual.dohParams, expected.dohParams);
+        assertFieldCountEquals(18, ResolverParamsParcel.class);
     }
 
     @Before
@@ -381,6 +382,7 @@
         expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
         expectedParams.resolverOptions = null;
         expectedParams.meteredNetwork = true;
+        expectedParams.dohParams = null;
         assertResolverParamsEquals(actualParams, expectedParams);
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 109c132..ea2228e 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -2039,16 +2039,7 @@
 
     private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
         setMockedUsers(PRIMARY_USER);
-
-        // Dummy egress interface
-        final LinkProperties lp = new LinkProperties();
-        lp.setInterfaceName(EGRESS_IFACE);
-
-        final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
-                        InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
-        lp.addRoute(defaultRoute);
-
-        vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
+        vpn.startLegacyVpn(vpnProfile);
         return vpn;
     }
 
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 4f5cfc0..f4c62c5 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -176,7 +176,7 @@
         override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator
         override fun getNetworkStack() = this@CSTest.networkStack
 
-        override fun makeHandlerThread() = csHandlerThread
+        override fun makeHandlerThread(tag: String) = csHandlerThread
         override fun makeProxyTracker(context: Context, connServiceHandler: Handler) = proxyTracker
 
         override fun makeCarrierPrivilegeAuthenticator(
diff --git a/thread/service/Android.bp b/thread/service/Android.bp
index bd265e6..35ae3c2 100644
--- a/thread/service/Android.bp
+++ b/thread/service/Android.bp
@@ -43,6 +43,9 @@
         "ot-daemon-aidl-java",
     ],
     apex_available: ["com.android.tethering"],
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
 }
 
 cc_library_shared {
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 33516aa..60c97bf 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -14,6 +14,10 @@
 
 package com.android.server.thread;
 
+import static android.net.MulticastRoutingConfig.CONFIG_FORWARD_NONE;
+import static android.net.MulticastRoutingConfig.FORWARD_NONE;
+import static android.net.MulticastRoutingConfig.FORWARD_SELECTED;
+import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE;
 import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
 import static android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID;
 import static android.net.thread.ActiveOperationalDataset.LENGTH_MESH_LOCAL_PREFIX_BITS;
@@ -51,13 +55,20 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
+import android.net.MulticastRoutingConfig;
+import android.net.LocalNetworkInfo;
+import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
+import android.net.NetworkRequest;
 import android.net.NetworkScore;
+import android.net.RouteInfo;
 import android.net.thread.ActiveOperationalDataset;
 import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
 import android.net.thread.IActiveOperationalDatasetReceiver;
@@ -85,8 +96,10 @@
 import com.android.server.thread.openthread.IOtStatusReceiver;
 import com.android.server.thread.openthread.Ipv6AddressInfo;
 import com.android.server.thread.openthread.OtDaemonState;
+import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
 
 import java.io.IOException;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.security.SecureRandom;
@@ -131,6 +144,14 @@
 
     private IOtDaemon mOtDaemon;
     private NetworkAgent mNetworkAgent;
+    private MulticastRoutingConfig mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+    private MulticastRoutingConfig mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+    private Network mUpstreamNetwork;
+    private final NetworkRequest mUpstreamNetworkRequest;
+    private final HashMap<Network, String> mNetworkToInterface;
+    private final LocalNetworkConfig mLocalNetworkConfig;
+
+    private BorderRouterConfigurationParcel mBorderRouterConfig;
 
     @VisibleForTesting
     ThreadNetworkControllerService(
@@ -147,6 +168,18 @@
         mOtDaemonSupplier = otDaemonSupplier;
         mConnectivityManager = connectivityManager;
         mTunIfController = tunIfController;
+        mUpstreamNetworkRequest =
+                new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+                        .build();
+        mLocalNetworkConfig =
+                new LocalNetworkConfig.Builder()
+                        .setUpstreamSelector(mUpstreamNetworkRequest)
+                        .build();
+        mNetworkToInterface = new HashMap<Network, String>();
+        mBorderRouterConfig = new BorderRouterConfigurationParcel();
     }
 
     public static ThreadNetworkControllerService newInstance(Context context) {
@@ -167,19 +200,24 @@
     private static NetworkCapabilities newNetworkCapabilities() {
         return new NetworkCapabilities.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .build();
     }
 
-    private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
+    private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
         try {
-            return InetAddress.getByAddress(addressInfo.address);
+            return (Inet6Address) Inet6Address.getByAddress(addressBytes);
         } catch (UnknownHostException e) {
-            // This is impossible unless the Thread daemon is critically broken
+            // This is unlikely to happen unless the Thread daemon is critically broken
             return null;
         }
     }
 
+    private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
+        return bytesToInet6Address(addressInfo.address);
+    }
+
     private static LinkAddress newLinkAddress(Ipv6AddressInfo addressInfo) {
         long deprecationTimeMillis =
                 addressInfo.isPreferred
@@ -244,11 +282,77 @@
                     mLinkProperties.setInterfaceName(TUN_IF_NAME);
                     mLinkProperties.setMtu(TunInterfaceController.MTU);
                     mConnectivityManager.registerNetworkProvider(mNetworkProvider);
+                    requestUpstreamNetwork();
 
                     initializeOtDaemon();
                 });
     }
 
+    private void requestUpstreamNetwork() {
+        mConnectivityManager.registerNetworkCallback(
+                mUpstreamNetworkRequest,
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onAvailable(@NonNull Network network) {
+                        Log.i(TAG, "onAvailable: " + network);
+                    }
+
+                    @Override
+                    public void onLost(@NonNull Network network) {
+                        Log.i(TAG, "onLost: " + network);
+                    }
+
+                    @Override
+                    public void onLinkPropertiesChanged(
+                            @NonNull Network network, @NonNull LinkProperties linkProperties) {
+                        Log.i(
+                                TAG,
+                                String.format(
+                                        "onLinkPropertiesChanged: {network: %s, interface: %s}",
+                                        network, linkProperties.getInterfaceName()));
+                        mNetworkToInterface.put(network, linkProperties.getInterfaceName());
+                        if (network.equals(mUpstreamNetwork)) {
+                            enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
+                        }
+                    }
+                },
+                mHandler);
+    }
+
+    private final class ThreadNetworkCallback extends ConnectivityManager.NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            Log.i(TAG, "onAvailable: Thread network Available");
+        }
+
+        @Override
+        public void onLocalNetworkInfoChanged(
+                @NonNull Network network, @NonNull LocalNetworkInfo localNetworkInfo) {
+            Log.i(TAG, "onLocalNetworkInfoChanged: " + localNetworkInfo);
+            if (localNetworkInfo.getUpstreamNetwork() == null) {
+                mUpstreamNetwork = null;
+                return;
+            }
+            if (!localNetworkInfo.getUpstreamNetwork().equals(mUpstreamNetwork)) {
+                mUpstreamNetwork = localNetworkInfo.getUpstreamNetwork();
+                if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
+                    enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
+                }
+            }
+        }
+    }
+
+    private void requestThreadNetwork() {
+        mConnectivityManager.registerNetworkCallback(
+                new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+                        .removeForbiddenCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
+                        .build(),
+                new ThreadNetworkCallback(),
+                mHandler);
+    }
+
     private void registerThreadNetwork() {
         if (mNetworkAgent != null) {
             return;
@@ -258,6 +362,7 @@
                 new NetworkScore.Builder()
                         .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
                         .build();
+        requestThreadNetwork();
         mNetworkAgent =
                 new NetworkAgent(
                         mContext,
@@ -265,6 +370,7 @@
                         TAG,
                         netCaps,
                         mLinkProperties,
+                        mLocalNetworkConfig,
                         score,
                         new NetworkAgentConfig.Builder().build(),
                         mNetworkProvider) {};
@@ -304,10 +410,19 @@
     }
 
     private void updateNetworkLinkProperties(LinkAddress linkAddress, boolean isAdded) {
+        RouteInfo routeInfo =
+                new RouteInfo(
+                        new IpPrefix(linkAddress.getAddress(), 64),
+                        null,
+                        TUN_IF_NAME,
+                        RouteInfo.RTN_UNICAST,
+                        TunInterfaceController.MTU);
         if (isAdded) {
             mLinkProperties.addLinkAddress(linkAddress);
+            mLinkProperties.addRoute(routeInfo);
         } else {
             mLinkProperties.removeLinkAddress(linkAddress);
+            mLinkProperties.removeRoute(routeInfo);
         }
 
         // The Thread daemon can send link property updates before the networkAgent is
@@ -557,6 +672,39 @@
         }
     }
 
+    private void enableBorderRouting(String infraIfName) {
+        if (mBorderRouterConfig.isBorderRoutingEnabled
+                && infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
+            return;
+        }
+        Log.i(TAG, "enableBorderRouting on AIL: " + infraIfName);
+        try {
+            mBorderRouterConfig.infraInterfaceName = infraIfName;
+            mBorderRouterConfig.infraInterfaceIcmp6Socket =
+                    InfraInterfaceController.createIcmp6Socket(infraIfName);
+            mBorderRouterConfig.isBorderRoutingEnabled = true;
+
+            mOtDaemon.configureBorderRouter(
+                    mBorderRouterConfig,
+                    new IOtStatusReceiver.Stub() {
+                        @Override
+                        public void onSuccess() {
+                            Log.i(TAG, "configure border router successfully");
+                        }
+
+                        @Override
+                        public void onError(int i, String s) {
+                            Log.w(
+                                    TAG,
+                                    String.format(
+                                            "failed to configure border router: %d %s", i, s));
+                        }
+                    });
+        } catch (Exception e) {
+            Log.w(TAG, "enableBorderRouting failed: " + e);
+        }
+    }
+
     private void handleThreadInterfaceStateChanged(boolean isUp) {
         try {
             mTunIfController.setInterfaceUp(isUp);
@@ -597,6 +745,100 @@
         updateNetworkLinkProperties(linkAddress, isAdded);
     }
 
+    private boolean isMulticastForwardingEnabled() {
+        return !(mUpstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE
+                && mDownstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE);
+    }
+
+    private void sendLocalNetworkConfig() {
+        if (mNetworkAgent == null) {
+            return;
+        }
+        final LocalNetworkConfig.Builder configBuilder = new LocalNetworkConfig.Builder();
+        LocalNetworkConfig localNetworkConfig =
+                configBuilder
+                        .setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig)
+                        .setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig)
+                        .setUpstreamSelector(mUpstreamNetworkRequest)
+                        .build();
+        mNetworkAgent.sendLocalNetworkConfig(localNetworkConfig);
+        Log.d(
+                TAG,
+                "Sent localNetworkConfig with upstreamConfig "
+                        + mUpstreamMulticastRoutingConfig
+                        + " downstreamConfig"
+                        + mDownstreamMulticastRoutingConfig);
+    }
+
+    private void handleMulticastForwardingStateChanged(boolean isEnabled) {
+        if (isMulticastForwardingEnabled() == isEnabled) {
+            return;
+        }
+        if (isEnabled) {
+            // When multicast forwarding is enabled, setup upstream forwarding to any address
+            // with minimal scope 4
+            // setup downstream forwarding with addresses subscribed from Thread network
+            mUpstreamMulticastRoutingConfig =
+                    new MulticastRoutingConfig.Builder(FORWARD_WITH_MIN_SCOPE, 4).build();
+            mDownstreamMulticastRoutingConfig =
+                    new MulticastRoutingConfig.Builder(FORWARD_SELECTED).build();
+        } else {
+            // When multicast forwarding is disabled, set both upstream and downstream
+            // forwarding config to FORWARD_NONE.
+            mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+            mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+        }
+        sendLocalNetworkConfig();
+        Log.d(
+                TAG,
+                "Sent updated localNetworkConfig with multicast forwarding "
+                        + (isEnabled ? "enabled" : "disabled"));
+    }
+
+    private void handleMulticastForwardingAddressChanged(byte[] addressBytes, boolean isAdded) {
+        Inet6Address address = bytesToInet6Address(addressBytes);
+        MulticastRoutingConfig newDownstreamConfig;
+        MulticastRoutingConfig.Builder builder;
+
+        if (mDownstreamMulticastRoutingConfig.getForwardingMode() !=
+                MulticastRoutingConfig.FORWARD_SELECTED) {
+            Log.e(
+                    TAG,
+                    "Ignore multicast listening address updates when downstream multicast "
+                            + "forwarding mode is not FORWARD_SELECTED");
+            // Don't update the address set if downstream multicast forwarding is disabled.
+            return;
+        }
+        if (isAdded ==
+                mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) {
+            return;
+        }
+
+        builder = new MulticastRoutingConfig.Builder(FORWARD_SELECTED);
+        for (Inet6Address listeningAddress :
+                mDownstreamMulticastRoutingConfig.getListeningAddresses()) {
+            builder.addListeningAddress(listeningAddress);
+        }
+
+        if (isAdded) {
+            builder.addListeningAddress(address);
+        } else {
+            builder.clearListeningAddress(address);
+        }
+
+        newDownstreamConfig = builder.build();
+        if (!newDownstreamConfig.equals(mDownstreamMulticastRoutingConfig)) {
+            Log.d(
+                    TAG,
+                    "Multicast listening address "
+                            + address.getHostAddress()
+                            + " is "
+                            + (isAdded ? "added" : "removed"));
+            mDownstreamMulticastRoutingConfig = newDownstreamConfig;
+            sendLocalNetworkConfig();
+        }
+    }
+
     private static final class CallbackMetadata {
         private static long gId = 0;
 
@@ -728,6 +970,7 @@
             onInterfaceStateChanged(newState.isInterfaceUp);
             onDeviceRoleChanged(newState.deviceRole, listenerId);
             onPartitionIdChanged(newState.partitionId, listenerId);
+            onMulticastForwardingStateChanged(newState.multicastForwardingEnabled);
             mState = newState;
 
             ActiveOperationalDataset newActiveDataset;
@@ -836,9 +1079,19 @@
             }
         }
 
+        private void onMulticastForwardingStateChanged(boolean isEnabled) {
+            checkOnHandlerThread();
+            handleMulticastForwardingStateChanged(isEnabled);
+        }
+
         @Override
         public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
             mHandler.post(() -> handleAddressChanged(addressInfo, isAdded));
         }
+
+        @Override
+        public void onMulticastForwardingAddressChanged(byte[] address, boolean isAdded) {
+            mHandler.post(() -> handleMulticastForwardingAddressChanged(address, isAdded));
+        }
     }
 }
diff --git a/thread/service/proguard.flags b/thread/service/proguard.flags
new file mode 100644
index 0000000..5028982
--- /dev/null
+++ b/thread/service/proguard.flags
@@ -0,0 +1,4 @@
+# Ensure the callback methods are not stripped
+-keepclassmembers class **.ThreadNetworkControllerService$ThreadNetworkCallback {
+    *;
+}