Merge "Add more unit tests for mobile data preferred uids feature" into sc-dev
diff --git a/Tethering/bpf_progs/Android.bp b/Tethering/bpf_progs/Android.bp
index 289d75d..5b00dfe 100644
--- a/Tethering/bpf_progs/Android.bp
+++ b/Tethering/bpf_progs/Android.bp
@@ -48,10 +48,6 @@
         "-Wall",
         "-Werror",
     ],
-    include_dirs: [
-        // TODO: get rid of system/netd.
-        "system/netd/bpf_progs",             // for bpf_net_helpers.h
-    ],
 }
 
 bpf {
@@ -61,8 +57,4 @@
         "-Wall",
         "-Werror",
     ],
-    include_dirs: [
-        // TODO: get rid of system/netd.
-        "system/netd/bpf_progs",             // for bpf_net_helpers.h
-    ],
 }
diff --git a/Tethering/bpf_progs/bpf_net_helpers.h b/Tethering/bpf_progs/bpf_net_helpers.h
new file mode 100644
index 0000000..c798580
--- /dev/null
+++ b/Tethering/bpf_progs/bpf_net_helpers.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <linux/bpf.h>
+#include <linux/if_packet.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+// this returns 0 iff skb->sk is NULL
+static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
+
+static uint32_t (*bpf_get_socket_uid)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_uid;
+
+static int (*bpf_skb_pull_data)(struct __sk_buff* skb, __u32 len) = (void*)BPF_FUNC_skb_pull_data;
+
+static int (*bpf_skb_load_bytes)(struct __sk_buff* skb, int off, void* to,
+                                 int len) = (void*)BPF_FUNC_skb_load_bytes;
+
+static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
+                                  __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
+
+static int64_t (*bpf_csum_diff)(__be32* from, __u32 from_size, __be32* to, __u32 to_size,
+                                __wsum seed) = (void*)BPF_FUNC_csum_diff;
+
+static int64_t (*bpf_csum_update)(struct __sk_buff* skb, __wsum csum) = (void*)BPF_FUNC_csum_update;
+
+static int (*bpf_skb_change_proto)(struct __sk_buff* skb, __be16 proto,
+                                   __u64 flags) = (void*)BPF_FUNC_skb_change_proto;
+static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
+                                  __u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
+static int (*bpf_l4_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
+                                  __u64 flags) = (void*)BPF_FUNC_l4_csum_replace;
+static int (*bpf_redirect)(__u32 ifindex, __u64 flags) = (void*)BPF_FUNC_redirect;
+static int (*bpf_redirect_map)(const struct bpf_map_def* map, __u32 key,
+                               __u64 flags) = (void*)BPF_FUNC_redirect_map;
+
+static int (*bpf_skb_change_head)(struct __sk_buff* skb, __u32 head_room,
+                                  __u64 flags) = (void*)BPF_FUNC_skb_change_head;
+static int (*bpf_skb_adjust_room)(struct __sk_buff* skb, __s32 len_diff, __u32 mode,
+                                  __u64 flags) = (void*)BPF_FUNC_skb_adjust_room;
+
+// Android only supports little endian architectures
+#define htons(x) (__builtin_constant_p(x) ? ___constant_swab16(x) : __builtin_bswap16(x))
+#define htonl(x) (__builtin_constant_p(x) ? ___constant_swab32(x) : __builtin_bswap32(x))
+#define ntohs(x) htons(x)
+#define ntohl(x) htonl(x)
+
+static inline __always_inline __unused bool is_received_skb(struct __sk_buff* skb) {
+    return skb->pkt_type == PACKET_HOST || skb->pkt_type == PACKET_BROADCAST ||
+           skb->pkt_type == PACKET_MULTICAST;
+}
+
+// try to make the first 'len' header bytes readable via direct packet access
+static inline __always_inline void try_make_readable(struct __sk_buff* skb, int len) {
+    if (len > skb->len) len = skb->len;
+    if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
+}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index c45ce83..3428c1d 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -1054,8 +1054,16 @@
         mLastRaParams = newParams;
     }
 
-    private void logMessage(State state, int what) {
-        mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
+    private void maybeLogMessage(State state, int what) {
+        switch (what) {
+            // Suppress some CMD_* to avoid log flooding.
+            case CMD_IPV6_TETHER_UPDATE:
+            case CMD_NEIGHBOR_EVENT:
+                break;
+            default:
+                mLog.log(state.getName() + " got "
+                        + sMagicDecoderRing.get(what, Integer.toString(what)));
+        }
     }
 
     private void sendInterfaceState(int newInterfaceState) {
@@ -1095,7 +1103,7 @@
 
         @Override
         public boolean processMessage(Message message) {
-            logMessage(this, message.what);
+            maybeLogMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -1180,7 +1188,6 @@
 
         @Override
         public boolean processMessage(Message message) {
-            logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_UNREQUESTED:
                     transitionTo(mInitialState);
@@ -1238,7 +1245,7 @@
         public boolean processMessage(Message message) {
             if (super.processMessage(message)) return true;
 
-            logMessage(this, message.what);
+            maybeLogMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode.");
@@ -1306,7 +1313,7 @@
         public boolean processMessage(Message message) {
             if (super.processMessage(message)) return true;
 
-            logMessage(this, message.what);
+            maybeLogMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLog.e("CMD_TETHER_REQUESTED while already tethering.");
@@ -1417,7 +1424,7 @@
     class WaitingForRestartState extends State {
         @Override
         public boolean processMessage(Message message) {
-            logMessage(this, message.what);
+            maybeLogMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_UNREQUESTED:
                     transitionTo(mInitialState);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 7596380..e4fbd71 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -495,11 +495,11 @@
         // See NetlinkHandler.cpp: notifyInterfaceChanged.
         if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
         if (up) {
-            maybeTrackNewInterfaceLocked(iface);
+            maybeTrackNewInterface(iface);
         } else {
             if (ifaceNameToType(iface) == TETHERING_BLUETOOTH
                     || ifaceNameToType(iface) == TETHERING_WIGIG) {
-                stopTrackingInterfaceLocked(iface);
+                stopTrackingInterface(iface);
             } else {
                 // Ignore usb0 down after enabling RNDIS.
                 // We will handle disconnect in interfaceRemoved.
@@ -535,12 +535,12 @@
 
     void interfaceAdded(String iface) {
         if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
-        maybeTrackNewInterfaceLocked(iface);
+        maybeTrackNewInterface(iface);
     }
 
     void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
-        stopTrackingInterfaceLocked(iface);
+        stopTrackingInterface(iface);
     }
 
     void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
@@ -694,14 +694,14 @@
             mEthernetCallback = new EthernetCallback();
             mEthernetIfaceRequest = em.requestTetheredInterface(mExecutor, mEthernetCallback);
         } else {
-            stopEthernetTetheringLocked();
+            stopEthernetTethering();
         }
         return TETHER_ERROR_NO_ERROR;
     }
 
-    private void stopEthernetTetheringLocked() {
+    private void stopEthernetTethering() {
         if (mConfiguredEthernetIface != null) {
-            stopTrackingInterfaceLocked(mConfiguredEthernetIface);
+            stopTrackingInterface(mConfiguredEthernetIface);
             mConfiguredEthernetIface = null;
         }
         if (mEthernetCallback != null) {
@@ -718,7 +718,7 @@
                 // Ethernet callback arrived after Ethernet tethering stopped. Ignore.
                 return;
             }
-            maybeTrackNewInterfaceLocked(iface, TETHERING_ETHERNET);
+            maybeTrackNewInterface(iface, TETHERING_ETHERNET);
             changeInterfaceState(iface, getRequestedState(TETHERING_ETHERNET));
             mConfiguredEthernetIface = iface;
         }
@@ -729,7 +729,7 @@
                 // onAvailable called after stopping Ethernet tethering.
                 return;
             }
-            stopEthernetTetheringLocked();
+            stopEthernetTethering();
         }
     }
 
@@ -1030,7 +1030,7 @@
                     // We can see this state on the way to both enabled and failure states.
                     break;
                 case WifiManager.WIFI_AP_STATE_ENABLED:
-                    enableWifiIpServingLocked(ifname, ipmode);
+                    enableWifiIpServing(ifname, ipmode);
                     break;
                 case WifiManager.WIFI_AP_STATE_DISABLING:
                     // We can see this state on the way to disabled.
@@ -1038,7 +1038,7 @@
                 case WifiManager.WIFI_AP_STATE_DISABLED:
                 case WifiManager.WIFI_AP_STATE_FAILED:
                 default:
-                    disableWifiIpServingLocked(ifname, curState);
+                    disableWifiIpServing(ifname, curState);
                     break;
             }
         }
@@ -1062,7 +1062,7 @@
 
             // if no group is formed, bring it down if needed.
             if (p2pInfo == null || !p2pInfo.groupFormed) {
-                disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
+                disableWifiP2pIpServingIfNeeded(mWifiP2pTetherInterface);
                 mWifiP2pTetherInterface = null;
                 return;
             }
@@ -1078,12 +1078,12 @@
                 mLog.w("P2P tethered interface " + mWifiP2pTetherInterface
                         + "is different from current interface "
                         + group.getInterface() + ", re-tether it");
-                disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
+                disableWifiP2pIpServingIfNeeded(mWifiP2pTetherInterface);
             }
 
             // Finally bring up serving on the new interface
             mWifiP2pTetherInterface = group.getInterface();
-            enableWifiIpServingLocked(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+            enableWifiIpServing(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
         }
 
         private void handleUserRestrictionAction() {
@@ -1164,7 +1164,7 @@
         }
     }
 
-    private void disableWifiIpServingLockedCommon(int tetheringType, String ifname, int apState) {
+    private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
         mLog.log("Canceling WiFi tethering request -"
                 + " type=" + tetheringType
                 + " interface=" + ifname
@@ -1191,23 +1191,23 @@
                                            : "specified interface: " + ifname));
     }
 
-    private void disableWifiIpServingLocked(String ifname, int apState) {
+    private void disableWifiIpServing(String ifname, int apState) {
         // Regardless of whether we requested this transition, the AP has gone
         // down.  Don't try to tether again unless we're requested to do so.
         // TODO: Remove this altogether, once Wi-Fi reliably gives us an
         // interface name with every broadcast.
         mWifiTetherRequested = false;
 
-        disableWifiIpServingLockedCommon(TETHERING_WIFI, ifname, apState);
+        disableWifiIpServingCommon(TETHERING_WIFI, ifname, apState);
     }
 
-    private void disableWifiP2pIpServingLockedIfNeeded(String ifname) {
+    private void disableWifiP2pIpServingIfNeeded(String ifname) {
         if (TextUtils.isEmpty(ifname)) return;
 
-        disableWifiIpServingLockedCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
+        disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
     }
 
-    private void enableWifiIpServingLocked(String ifname, int wifiIpMode) {
+    private void enableWifiIpServing(String ifname, int wifiIpMode) {
         // Map wifiIpMode values to IpServer.Callback serving states, inferring
         // from mWifiTetherRequested as a final "best guess".
         final int ipServingMode;
@@ -1224,7 +1224,7 @@
         }
 
         if (!TextUtils.isEmpty(ifname)) {
-            maybeTrackNewInterfaceLocked(ifname);
+            maybeTrackNewInterface(ifname);
             changeInterfaceState(ifname, ipServingMode);
         } else {
             mLog.e(String.format(
@@ -2437,7 +2437,7 @@
         mTetherMainSM.sendMessage(which, state, 0, newLp);
     }
 
-    private void maybeTrackNewInterfaceLocked(final String iface) {
+    private void maybeTrackNewInterface(final String iface) {
         // If we don't care about this type of interface, ignore.
         final int interfaceType = ifaceNameToType(iface);
         if (interfaceType == TETHERING_INVALID) {
@@ -2457,10 +2457,10 @@
             return;
         }
 
-        maybeTrackNewInterfaceLocked(iface, interfaceType);
+        maybeTrackNewInterface(iface, interfaceType);
     }
 
-    private void maybeTrackNewInterfaceLocked(final String iface, int interfaceType) {
+    private void maybeTrackNewInterface(final String iface, int interfaceType) {
         // If we have already started a TISM for this interface, skip.
         if (mTetherStates.containsKey(iface)) {
             mLog.log("active iface (" + iface + ") reported as added, ignoring");
@@ -2477,7 +2477,7 @@
         tetherState.ipServer.start();
     }
 
-    private void stopTrackingInterfaceLocked(final String iface) {
+    private void stopTrackingInterface(final String iface) {
         final TetherState tetherState = mTetherStates.get(iface);
         if (tetherState == null) {
             mLog.log("attempting to remove unknown iface (" + iface + "), ignoring");
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index e615334..485eec8 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -398,10 +398,10 @@
             // notifications (e.g. matching more than one of our callbacks).
             //
             // Also, it can happen that onLinkPropertiesChanged is called after
-            // onLost removed the state from mNetworkMap. This appears to be due
-            // to a bug in disconnectAndDestroyNetwork, which calls
-            // nai.clatd.update() after the onLost callbacks.
-            // TODO: fix the bug and make this method void.
+            // onLost removed the state from mNetworkMap. This is due to a bug
+            // in disconnectAndDestroyNetwork, which calls nai.clatd.update()
+            // after the onLost callbacks. This was fixed in S.
+            // TODO: make this method void when R is no longer supported.
             return null;
         }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
index 6090213..b8389ea 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
@@ -36,6 +36,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.util.Map;
@@ -67,10 +68,10 @@
     public static final boolean BROADCAST_FIRST = false;
     public static final boolean CALLBACKS_FIRST = true;
 
-    final Map<NetworkCallback, NetworkRequestInfo> mAllCallbacks = new ArrayMap<>();
+    final Map<NetworkCallback, NetworkCallbackInfo> mAllCallbacks = new ArrayMap<>();
     // This contains the callbacks tracking the system default network, whether it's registered
     // with registerSystemDefaultNetworkCallback (S+) or with a custom request (R-).
-    final Map<NetworkCallback, NetworkRequestInfo> mTrackingDefault = new ArrayMap<>();
+    final Map<NetworkCallback, NetworkCallbackInfo> mTrackingDefault = new ArrayMap<>();
     final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>();
     final Map<NetworkCallback, NetworkRequestInfo> mRequested = new ArrayMap<>();
     final Map<NetworkCallback, Integer> mLegacyTypeMap = new ArrayMap<>();
@@ -91,15 +92,21 @@
         mContext = ctx;
     }
 
-    class NetworkRequestInfo {
-        public final NetworkRequest request;
+    static class NetworkCallbackInfo {
         public final Handler handler;
-        NetworkRequestInfo(NetworkRequest r, Handler h) {
-            request = r;
+        NetworkCallbackInfo(Handler h) {
             handler = h;
         }
     }
 
+    static class NetworkRequestInfo extends NetworkCallbackInfo {
+        public final NetworkRequest request;
+        NetworkRequestInfo(NetworkRequest r, Handler h) {
+            super(h);
+            request = r;
+        }
+    }
+
     boolean hasNoCallbacks() {
         return mAllCallbacks.isEmpty()
                 && mTrackingDefault.isEmpty()
@@ -145,7 +152,7 @@
     private void sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault,
             TestNetworkAgent defaultNetwork) {
         for (NetworkCallback cb : mTrackingDefault.keySet()) {
-            final NetworkRequestInfo nri = mTrackingDefault.get(cb);
+            final NetworkCallbackInfo nri = mTrackingDefault.get(cb);
             if (defaultNetwork != null) {
                 nri.handler.post(() -> cb.onAvailable(defaultNetwork.networkId));
                 nri.handler.post(() -> cb.onCapabilitiesChanged(
@@ -191,20 +198,28 @@
 
     @Override
     public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
-        assertFalse(mAllCallbacks.containsKey(cb));
-        mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
         // For R- devices, Tethering will invoke this function in 2 cases, one is to request mobile
         // network, the other is to track system default network.
         if (looksLikeDefaultRequest(req)) {
-            assertFalse(mTrackingDefault.containsKey(cb));
-            mTrackingDefault.put(cb, new NetworkRequestInfo(req, h));
+            registerSystemDefaultNetworkCallback(cb, h);
         } else {
+            assertFalse(mAllCallbacks.containsKey(cb));
+            mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
             assertFalse(mRequested.containsKey(cb));
             mRequested.put(cb, new NetworkRequestInfo(req, h));
         }
     }
 
     @Override
+    public void registerSystemDefaultNetworkCallback(
+            @NonNull NetworkCallback cb, @NonNull Handler h) {
+        assertFalse(mAllCallbacks.containsKey(cb));
+        mAllCallbacks.put(cb, new NetworkCallbackInfo(h));
+        assertFalse(mTrackingDefault.containsKey(cb));
+        mTrackingDefault.put(cb, new NetworkCallbackInfo(h));
+    }
+
+    @Override
     public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
         fail("Should never be called.");
     }
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 f1d8c0f..c636384 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -60,6 +60,7 @@
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
@@ -779,10 +780,17 @@
     }
 
     private void verifyDefaultNetworkRequestFiled() {
-        ArgumentCaptor<NetworkRequest> reqCaptor = ArgumentCaptor.forClass(NetworkRequest.class);
-        verify(mCm, times(1)).requestNetwork(reqCaptor.capture(),
-                any(NetworkCallback.class), any(Handler.class));
-        assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
+        if (isAtLeastS()) {
+            verify(mCm, times(1)).registerSystemDefaultNetworkCallback(
+                    any(NetworkCallback.class), any(Handler.class));
+        } else {
+            ArgumentCaptor<NetworkRequest> reqCaptor = ArgumentCaptor.forClass(
+                    NetworkRequest.class);
+            verify(mCm, times(1)).requestNetwork(reqCaptor.capture(),
+                    any(NetworkCallback.class), any(Handler.class));
+            assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
+        }
+
         // The default network request is only ever filed once.
         verifyNoMoreInteractions(mCm);
     }
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 ce4ba85..173679d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -24,6 +24,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.networkstack.tethering.UpstreamNetworkMonitor.TYPE_NONE;
 
 import static org.junit.Assert.assertEquals;
@@ -172,12 +173,17 @@
         // Verify the fired default request matches expectation.
         final ArgumentCaptor<NetworkRequest> requestCaptor =
                 ArgumentCaptor.forClass(NetworkRequest.class);
-        verify(mCM, times(1)).requestNetwork(
-                requestCaptor.capture(), any(NetworkCallback.class), any(Handler.class));
-        // For R- devices, Tethering will invoke this function in 2 cases, one is to
-        // request mobile network, the other is to track system default network. Verify
-        // the request is the one tracks default network.
-        assertTrue(TestConnectivityManager.looksLikeDefaultRequest(requestCaptor.getValue()));
+
+        if (isAtLeastS()) {
+            verify(mCM).registerSystemDefaultNetworkCallback(any(), any());
+        } else {
+            verify(mCM).requestNetwork(
+                    requestCaptor.capture(), any(NetworkCallback.class), any(Handler.class));
+            // For R- devices, Tethering will invoke this function in 2 cases, one is to
+            // request mobile network, the other is to track system default network. Verify
+            // the request is the one tracks default network.
+            assertTrue(TestConnectivityManager.looksLikeDefaultRequest(requestCaptor.getValue()));
+        }
 
         mUNM.startObserveAllNetworks();
         verify(mCM, times(1)).registerNetworkCallback(
diff --git a/service/ServiceConnectivityResources/res/values-ky/strings.xml b/service/ServiceConnectivityResources/res/values-ky/strings.xml
index 9ff53cf..0027c8a 100644
--- a/service/ServiceConnectivityResources/res/values-ky/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ky/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Тутумдун байланыш булагы"</string>
+    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Системанын байланыш булагы"</string>
     <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi түйүнүнө кирүү"</string>
     <string name="network_available_sign_in" msgid="2622520134876355561">"Тармакка кирүү"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 0d0ed17..ba61159 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -6307,8 +6307,7 @@
     // there may not be a strict 1:1 correlation between the two.
     private final NetIdManager mNetIdManager;
 
-    // NetworkAgentInfo keyed off its connecting messenger
-    // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
+    // Tracks all NetworkAgents that are currently registered.
     // NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
     private final ArraySet<NetworkAgentInfo> mNetworkAgentInfos = new ArraySet<>();
 
@@ -7451,7 +7450,7 @@
     public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
         ensureRunningOnConnectivityServiceThread();
 
-        if (getNetworkAgentInfoForNetId(nai.network.getNetId()) != nai) {
+        if (!mNetworkAgentInfos.contains(nai)) {
             // Ignore updates for disconnected networks
             return;
         }
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 10e43e7..c54ee91 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -16,6 +16,14 @@
 
 package android.net.cts;
 
+import static android.net.IpSecAlgorithm.AUTH_CRYPT_AES_GCM;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_MD5;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA1;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA256;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
+import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
+
 import static org.junit.Assert.assertArrayEquals;
 
 import android.content.Context;
@@ -31,6 +39,12 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramPacket;
@@ -42,12 +56,10 @@
 import java.net.Socket;
 import java.net.SocketException;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 public class IpSecBaseTest {
 
@@ -71,6 +83,18 @@
         0x20, 0x21, 0x22, 0x23
     };
 
+    private static final Set<String> MANDATORY_IPSEC_ALGOS_SINCE_P = new HashSet<>();
+
+    static {
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(CRYPT_AES_CBC);
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_MD5);
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA1);
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA256);
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA384);
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_HMAC_SHA512);
+        MANDATORY_IPSEC_ALGOS_SINCE_P.add(AUTH_CRYPT_AES_GCM);
+    }
+
     protected static final byte[] AUTH_KEY = getKey(256);
     protected static final byte[] CRYPT_KEY = getKey(256);
 
@@ -89,8 +113,24 @@
                                 .getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    /** Checks if an IPsec algorithm is enabled on the device */
+    protected static boolean hasIpSecAlgorithm(String algorithm) {
+        if (SdkLevel.isAtLeastS()) {
+            return IpSecAlgorithm.getSupportedAlgorithms().contains(algorithm);
+        } else {
+            return MANDATORY_IPSEC_ALGOS_SINCE_P.contains(algorithm);
+        }
+    }
+
+    protected static byte[] getKeyBytes(int byteLength) {
+        return Arrays.copyOf(KEY_DATA, byteLength);
+    }
+
     protected static byte[] getKey(int bitLength) {
-        return Arrays.copyOf(KEY_DATA, bitLength / 8);
+        if (bitLength % 8 != 0) {
+            throw new IllegalArgumentException("Invalid key length in bits" + bitLength);
+        }
+        return getKeyBytes(bitLength / 8);
     }
 
     protected static int getDomain(InetAddress address) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index d08f6e9..e7e1d67 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -16,10 +16,33 @@
 
 package android.net.cts;
 
+import static android.net.IpSecAlgorithm.AUTH_AES_CMAC;
+import static android.net.IpSecAlgorithm.AUTH_AES_XCBC;
+import static android.net.IpSecAlgorithm.AUTH_CRYPT_AES_GCM;
+import static android.net.IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_MD5;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA1;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA256;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
+import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
+import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
+import static android.net.IpSecAlgorithm.CRYPT_AES_CTR;
 import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
 import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
+import static android.net.cts.PacketUtils.AES_CMAC_ICV_LEN;
+import static android.net.cts.PacketUtils.AES_CMAC_KEY_LEN;
+import static android.net.cts.PacketUtils.AES_CTR_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_CTR_IV_LEN;
+import static android.net.cts.PacketUtils.AES_CTR_KEY_LEN;
 import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE;
 import static android.net.cts.PacketUtils.AES_GCM_IV_LEN;
+import static android.net.cts.PacketUtils.AES_XCBC_ICV_LEN;
+import static android.net.cts.PacketUtils.AES_XCBC_KEY_LEN;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_BLK_SIZE;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_ICV_LEN;
+import static android.net.cts.PacketUtils.CHACHA20_POLY1305_IV_LEN;
+import static android.net.cts.PacketUtils.HMAC_SHA512_ICV_LEN;
+import static android.net.cts.PacketUtils.HMAC_SHA512_KEY_LEN;
 import static android.net.cts.PacketUtils.IP4_HDRLEN;
 import static android.net.cts.PacketUtils.IP6_HDRLEN;
 import static android.net.cts.PacketUtils.TCP_HDRLEN_WITH_TIMESTAMP_OPT;
@@ -27,15 +50,20 @@
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
 
+import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
+import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
 import android.net.TrafficStats;
+import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -44,8 +72,11 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.SkipPresubmit;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -56,10 +87,15 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "Socket cannot bind in instant app mode")
 public class IpSecManagerTest extends IpSecBaseTest {
+    @Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
     private static final String TAG = IpSecManagerTest.class.getSimpleName();
 
@@ -417,8 +453,12 @@
         switch (cryptOrAead.getName()) {
             case IpSecAlgorithm.CRYPT_AES_CBC:
                 return AES_CBC_IV_LEN;
+            case IpSecAlgorithm.CRYPT_AES_CTR:
+                return AES_CTR_IV_LEN;
             case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
                 return AES_GCM_IV_LEN;
+            case IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305:
+                return CHACHA20_POLY1305_IV_LEN;
             default:
                 throw new IllegalArgumentException(
                         "IV length unknown for algorithm" + cryptOrAead.getName());
@@ -433,8 +473,12 @@
         switch (cryptOrAead.getName()) {
             case IpSecAlgorithm.CRYPT_AES_CBC:
                 return AES_CBC_BLK_SIZE;
+            case IpSecAlgorithm.CRYPT_AES_CTR:
+                return AES_CTR_BLK_SIZE;
             case IpSecAlgorithm.AUTH_CRYPT_AES_GCM:
                 return AES_GCM_BLK_SIZE;
+            case IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305:
+                return CHACHA20_POLY1305_BLK_SIZE;
             default:
                 throw new IllegalArgumentException(
                         "Blk size unknown for algorithm" + cryptOrAead.getName());
@@ -516,7 +560,6 @@
             int blkSize,
             int truncLenBits)
             throws Exception {
-
         int innerPacketSize = TEST_DATA.length + transportHdrLen + ipHdrLen;
         int outerPacketSize =
                 PacketUtils.calculateEspPacketSize(
@@ -663,6 +706,41 @@
     //     checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1000);
     // }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testGetSupportedAlgorithms() throws Exception {
+        final Map<String, Integer> algoToRequiredMinSdk = new HashMap<>();
+        algoToRequiredMinSdk.put(CRYPT_AES_CBC, Build.VERSION_CODES.P);
+        algoToRequiredMinSdk.put(AUTH_HMAC_MD5, Build.VERSION_CODES.P);
+        algoToRequiredMinSdk.put(AUTH_HMAC_SHA1, Build.VERSION_CODES.P);
+        algoToRequiredMinSdk.put(AUTH_HMAC_SHA256, Build.VERSION_CODES.P);
+        algoToRequiredMinSdk.put(AUTH_HMAC_SHA384, Build.VERSION_CODES.P);
+        algoToRequiredMinSdk.put(AUTH_HMAC_SHA512, Build.VERSION_CODES.P);
+        algoToRequiredMinSdk.put(AUTH_CRYPT_AES_GCM, Build.VERSION_CODES.P);
+
+        // TODO: b/170424293 Use Build.VERSION_CODES.S when is finalized
+        algoToRequiredMinSdk.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1);
+        algoToRequiredMinSdk.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1);
+        algoToRequiredMinSdk.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1);
+        algoToRequiredMinSdk.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1);
+
+        final Set<String> supportedAlgos = IpSecAlgorithm.getSupportedAlgorithms();
+
+        // Verify all supported algorithms are valid
+        for (String algo : supportedAlgos) {
+            assertTrue("Found invalid algo " + algo, algoToRequiredMinSdk.keySet().contains(algo));
+        }
+
+        // Verify all mandatory algorithms are supported
+        for (Entry<String, Integer> entry : algoToRequiredMinSdk.entrySet()) {
+            if (Math.min(getFirstApiLevel(), getVendorApiLevel()) >= entry.getValue()) {
+                assertTrue(
+                        "Fail to support " + entry.getKey(),
+                        supportedAlgos.contains(entry.getKey()));
+            }
+        }
+    }
+
     @Test
     public void testInterfaceCountersUdp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
@@ -849,6 +927,152 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    private static IpSecAlgorithm buildCryptAesCtr() throws Exception {
+        return new IpSecAlgorithm(CRYPT_AES_CTR, getKeyBytes(AES_CTR_KEY_LEN));
+    }
+
+    private static IpSecAlgorithm buildAuthHmacSha512() throws Exception {
+        return new IpSecAlgorithm(
+                AUTH_HMAC_SHA512, getKeyBytes(HMAC_SHA512_KEY_LEN), HMAC_SHA512_ICV_LEN * 8);
+    }
+
+    @Test
+    public void testAesCtrHmacSha512Tcp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+        final IpSecAlgorithm crypt = buildCryptAesCtr();
+        final IpSecAlgorithm auth = buildAuthHmacSha512();
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
+    public void testAesCtrHmacSha512Tcp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+        final IpSecAlgorithm crypt = buildCryptAesCtr();
+        final IpSecAlgorithm auth = buildAuthHmacSha512();
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCtrHmacSha512Udp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+        final IpSecAlgorithm crypt = buildCryptAesCtr();
+        final IpSecAlgorithm auth = buildAuthHmacSha512();
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCtrHmacSha512Udp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+        final IpSecAlgorithm crypt = buildCryptAesCtr();
+        final IpSecAlgorithm auth = buildAuthHmacSha512();
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    private static IpSecAlgorithm buildCryptAesCbc() throws Exception {
+        return new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
+    }
+
+    private static IpSecAlgorithm buildAuthAesXcbc() throws Exception {
+        return new IpSecAlgorithm(
+                AUTH_AES_XCBC, getKeyBytes(AES_XCBC_KEY_LEN), AES_XCBC_ICV_LEN * 8);
+    }
+
+    private static IpSecAlgorithm buildAuthAesCmac() throws Exception {
+        return new IpSecAlgorithm(
+                AUTH_AES_CMAC, getKeyBytes(AES_CMAC_KEY_LEN), AES_CMAC_ICV_LEN * 8);
+    }
+
+    @Test
+    public void testAesCbcAesXCbcTcp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesXcbc();
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
+    public void testAesCbcAesXCbcTcp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesXcbc();
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesXCbcUdp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesXcbc();
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesXCbcUdp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesXcbc();
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesCmacTcp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesCmac();
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
+    public void testAesCbcAesCmacTcp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesCmac();
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesCmacUdp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesCmac();
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesCmacUdp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
+
+        final IpSecAlgorithm crypt = buildCryptAesCbc();
+        final IpSecAlgorithm auth = buildAuthAesCmac();
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
+    }
+
     @Test
     public void testAesGcm64Tcp4() throws Exception {
         IpSecAlgorithm authCrypt =
@@ -948,6 +1172,48 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    private static IpSecAlgorithm buildAuthCryptChaCha20Poly1305() throws Exception {
+        return new IpSecAlgorithm(
+                AUTH_CRYPT_CHACHA20_POLY1305, AEAD_KEY, CHACHA20_POLY1305_ICV_LEN * 8);
+    }
+
+    @Test
+    public void testChaCha20Poly1305Tcp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+        final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    @Test
+    @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
+    public void testChaCha20Poly1305Tcp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+        final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    @Test
+    public void testChaCha20Poly1305Udp4() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+        final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
+    @Test
+    public void testChaCha20Poly1305Udp6() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+        final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, false);
+        checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
+    }
+
     @Test
     public void testAesCbcHmacMd5Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
@@ -1029,6 +1295,66 @@
     }
 
     @Test
+    public void testAesCtrHmacSha512Tcp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+        final IpSecAlgorithm crypt = buildCryptAesCtr();
+        final IpSecAlgorithm auth = buildAuthHmacSha512();
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    @Test
+    public void testAesCtrHmacSha512Udp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(CRYPT_AES_CTR));
+
+        final IpSecAlgorithm crypt = buildCryptAesCtr();
+        final IpSecAlgorithm auth = buildAuthHmacSha512();
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesXCbcTcp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+        final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
+        final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_XCBC, getKey(128), 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesXCbcUdp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_XCBC));
+
+        final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
+        final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_XCBC, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesCmacTcp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
+
+        final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
+        final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_CMAC, getKey(128), 96);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    @Test
+    public void testAesCbcAesCmacUdp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_AES_CMAC));
+
+        final IpSecAlgorithm crypt = new IpSecAlgorithm(CRYPT_AES_CBC, CRYPT_KEY);
+        final IpSecAlgorithm auth = new IpSecAlgorithm(AUTH_AES_CMAC, getKey(128), 96);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
+    }
+
+    @Test
     public void testAesGcm64Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -1077,6 +1403,24 @@
     }
 
     @Test
+    public void testChaCha20Poly1305Tcp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+        final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    @Test
+    public void testChaCha20Poly1305Udp4UdpEncap() throws Exception {
+        assumeTrue(hasIpSecAlgorithm(AUTH_CRYPT_CHACHA20_POLY1305));
+
+        final IpSecAlgorithm authCrypt = buildAuthCryptChaCha20Poly1305();
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, false);
+        checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
+    }
+
+    @Test
     public void testCryptUdp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java
index 0aedecb..7e622f6 100644
--- a/tests/cts/net/src/android/net/cts/PacketUtils.java
+++ b/tests/cts/net/src/android/net/cts/PacketUtils.java
@@ -43,17 +43,35 @@
     static final int UDP_HDRLEN = 8;
     static final int TCP_HDRLEN = 20;
     static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
+    static final int ESP_BLK_SIZE = 4; // ESP has to be 4-byte aligned
 
     // Not defined in OsConstants
     static final int IPPROTO_IPV4 = 4;
     static final int IPPROTO_ESP = 50;
 
     // Encryption parameters
-    static final int AES_GCM_IV_LEN = 8;
     static final int AES_CBC_IV_LEN = 16;
-    static final int AES_GCM_BLK_SIZE = 4;
     static final int AES_CBC_BLK_SIZE = 16;
 
+    static final int AES_CTR_KEY_LEN = 20;
+    static final int AES_CTR_BLK_SIZE = ESP_BLK_SIZE;
+    static final int AES_CTR_IV_LEN = 8;
+
+    // AEAD parameters
+    static final int AES_GCM_IV_LEN = 8;
+    static final int AES_GCM_BLK_SIZE = 4;
+    static final int CHACHA20_POLY1305_BLK_SIZE = ESP_BLK_SIZE;
+    static final int CHACHA20_POLY1305_IV_LEN = 8;
+    static final int CHACHA20_POLY1305_ICV_LEN = 16;
+
+    // Authentication parameters
+    static final int HMAC_SHA512_KEY_LEN = 64;
+    static final int HMAC_SHA512_ICV_LEN = 32;
+    static final int AES_XCBC_KEY_LEN = 16;
+    static final int AES_XCBC_ICV_LEN = 12;
+    static final int AES_CMAC_KEY_LEN = 16;
+    static final int AES_CMAC_ICV_LEN = 12;
+
     // Encryption algorithms
     static final String AES = "AES";
     static final String AES_CBC = "AES/CBC/NoPadding";
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index 88a2068..b5f1208 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -26,5 +26,6 @@
         "compatibility-device-util-axt",
         "junit",
         "net-tests-utils",
+        "modules-utils-build",
     ],
 }
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 041a9cb..b32218b 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -23,6 +23,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION;
 
+import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
 import static org.junit.Assert.assertEquals;
@@ -55,7 +56,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
-import android.os.SystemProperties;
 import android.provider.Settings;
 import android.system.Os;
 import android.system.OsConstants;
@@ -117,8 +117,7 @@
     /** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */
     public boolean hasIpsecTunnelsFeature() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
-                || SystemProperties.getInt("ro.product.first_api_level", 0)
-                        >= Build.VERSION_CODES.Q;
+                || getFirstApiLevel() >= Build.VERSION_CODES.Q;
     }
 
     /**
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 77cfe23..d12726e 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -412,6 +412,7 @@
             UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID);
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
+    private static final String CLAT_MOBILE_IFNAME = CLAT_PREFIX + MOBILE_IFNAME;
     private static final String WIFI_IFNAME = "test_wlan0";
     private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
     private static final String VPN_IFNAME = "tun10042";
@@ -8478,10 +8479,10 @@
     private LinkProperties makeClatLinkProperties(LinkAddress la) {
         LinkAddress clatAddress = la;
         LinkProperties stacked = new LinkProperties();
-        stacked.setInterfaceName(CLAT_PREFIX + MOBILE_IFNAME);
+        stacked.setInterfaceName(CLAT_MOBILE_IFNAME);
         RouteInfo ipv4Default = new RouteInfo(
                 new LinkAddress(Inet4Address.ANY, 0),
-                clatAddress.getAddress(), CLAT_PREFIX + MOBILE_IFNAME);
+                clatAddress.getAddress(), CLAT_MOBILE_IFNAME);
         stacked.addRoute(ipv4Default);
         stacked.addLinkAddress(clatAddress);
         return stacked;
@@ -8506,12 +8507,12 @@
         final String kOtherNat64PrefixString = "64:ff9b::";
         final IpPrefix kOtherNat64Prefix = new IpPrefix(
                 InetAddress.getByName(kOtherNat64PrefixString), 96);
-        final RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, myIpv6.getAddress(),
-                                                     MOBILE_IFNAME);
+        final RouteInfo ipv6Default =
+                new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME);
         final RouteInfo ipv6Subnet = new RouteInfo(myIpv6, null, MOBILE_IFNAME);
         final RouteInfo ipv4Subnet = new RouteInfo(myIpv4, null, MOBILE_IFNAME);
-        final RouteInfo stackedDefault = new RouteInfo((IpPrefix) null, myIpv4.getAddress(),
-                                                       CLAT_PREFIX + MOBILE_IFNAME);
+        final RouteInfo stackedDefault =
+                new RouteInfo((IpPrefix) null, myIpv4.getAddress(), CLAT_MOBILE_IFNAME);
 
         final NetworkRequest networkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
@@ -8524,7 +8525,7 @@
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
         cellLp.addLinkAddress(myIpv6);
-        cellLp.addRoute(defaultRoute);
+        cellLp.addRoute(ipv6Default);
         cellLp.addRoute(ipv6Subnet);
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
         reset(mMockDnsResolver);
@@ -8532,12 +8533,12 @@
 
         // Connect with ipv6 link properties. Expect prefix discovery to be started.
         mCellNetworkAgent.connect(true);
-        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        int cellNetId = mCellNetworkAgent.getNetwork().netId;
         waitForIdle();
 
         verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId,
                 INetd.PERMISSION_NONE));
-        assertRoutesAdded(cellNetId, ipv6Subnet, defaultRoute);
+        assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default);
         verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME);
         verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext,
@@ -8568,7 +8569,7 @@
         verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockNetd);
         reset(mMockDnsResolver);
-        when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME))
+        when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME))
                 .thenReturn(getClatInterfaceConfigParcel(myIpv4));
 
         // Remove IPv4 address. Expect prefix discovery to be started again.
@@ -8590,13 +8591,13 @@
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
         // Clat iface comes up. Expect stacked link to be added.
-        clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+        clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
         networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
                 .getStackedLinks();
         assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
         assertRoutesAdded(cellNetId, stackedDefault);
-        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
         // Change trivial linkproperties and see if stacked link is preserved.
         cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
         mCellNetworkAgent.sendLinkProperties(cellLp);
@@ -8619,7 +8620,7 @@
                     new int[] { TRANSPORT_CELLULAR });
         }
         reset(mMockNetd);
-        when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME))
+        when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME))
                 .thenReturn(getClatInterfaceConfigParcel(myIpv4));
         // Change the NAT64 prefix without first removing it.
         // Expect clatd to be stopped and started with the new prefix.
@@ -8629,16 +8630,16 @@
                 (lp) -> lp.getStackedLinks().size() == 0);
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
         assertRoutesRemoved(cellNetId, stackedDefault);
-        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
 
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString());
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix));
-        clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+        clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getStackedLinks().size() == 1);
         assertRoutesAdded(cellNetId, stackedDefault);
-        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
         reset(mMockNetd);
 
         // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
@@ -8661,14 +8662,14 @@
         assertRoutesRemoved(cellNetId, stackedDefault);
 
         // The interface removed callback happens but has no effect after stop is called.
-        clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
+        clat.interfaceRemoved(CLAT_MOBILE_IFNAME);
         networkCallback.assertNoCallback();
-        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockNetd);
         reset(mMockDnsResolver);
-        when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME))
+        when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME))
                 .thenReturn(getClatInterfaceConfigParcel(myIpv4));
 
         // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
@@ -8691,11 +8692,11 @@
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
         // Clat iface comes up. Expect stacked link to be added.
-        clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+        clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null);
         assertRoutesAdded(cellNetId, stackedDefault);
-        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
 
         // NAT64 prefix is removed. Expect that clat is stopped.
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
@@ -8708,13 +8709,54 @@
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
                 (lp) -> lp.getStackedLinks().size() == 0);
-        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME);
-        verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME);
-        verifyNoMoreInteractions(mMockNetd);
+        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_MOBILE_IFNAME);
         // Clean up.
         mCellNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         networkCallback.assertNoCallback();
+        verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
+        verify(mMockNetd).networkDestroy(cellNetId);
+        verifyNoMoreInteractions(mMockNetd);
+        reset(mMockNetd);
+
+        // Test disconnecting a network that is running 464xlat.
+
+        // Connect a network with a NAT64 prefix.
+        when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME))
+                .thenReturn(getClatInterfaceConfigParcel(myIpv4));
+        cellLp.setNat64Prefix(kNat64Prefix);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellNetworkAgent.connect(false /* validated */);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        cellNetId = mCellNetworkAgent.getNetwork().netId;
+        verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId,
+                INetd.PERMISSION_NONE));
+        assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default);
+
+        // Clatd is started and clat iface comes up. Expect stacked link to be added.
+        verify(mMockNetd).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
+        clat = getNat464Xlat(mCellNetworkAgent);
+        clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true /* up */);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 1
+                        && lp.getNat64Prefix().equals(kNat64Prefix));
+        verify(mMockNetd).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
+        // assertRoutesAdded sees all calls since last mMockNetd reset, so expect IPv6 routes again.
+        assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default, stackedDefault);
+        reset(mMockNetd);
+
+        // Disconnect the network. clat is stopped and the network is destroyed.
+        mCellNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        networkCallback.assertNoCallback();
+        verify(mMockNetd).clatdStop(MOBILE_IFNAME);
+        verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+                eq(Integer.toString(TRANSPORT_CELLULAR)));
+        verify(mMockNetd).networkDestroy(cellNetId);
+        verifyNoMoreInteractions(mMockNetd);
+
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
@@ -9859,6 +9901,8 @@
     }
 
     private void assertRoutesAdded(int netId, RouteInfo... routes) throws Exception {
+        // TODO: add @JavaDerive(equals=true) to RouteInfoParcel, use eq() directly, and delete
+        // assertRouteInfoParcelMatches above.
         ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class);
         verify(mMockNetd, times(routes.length)).networkAddRouteParcel(eq(netId), captor.capture());
         for (int i = 0; i < routes.length; i++) {