Merge "Prevent hotspot from being affected by the wifi retention feature"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 83ca2b7..b88ec7f 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -119,7 +119,6 @@
     name: "libcom_android_networkstack_tethering_util_jni",
     sdk_version: "30",
     apex_available: [
-        "//apex_available:platform", // Used by InProcessTethering
         "com.android.tethering",
     ],
     min_sdk_version: "30",
@@ -188,24 +187,6 @@
     lint: { strict_updatability_linting: true },
 }
 
-// Non-updatable tethering running in the system server process for devices not using the module
-android_app {
-    name: "InProcessTethering",
-    defaults: [
-        "TetheringAppDefaults",
-        "TetheringApiLevel",
-        "ConnectivityNextEnableDefaults",
-        "TetheringReleaseTargetSdk"
-    ],
-    static_libs: ["TetheringApiCurrentLib"],
-    certificate: "platform",
-    manifest: "AndroidManifest_InProcess.xml",
-    // InProcessTethering is a replacement for Tethering
-    overrides: ["Tethering"],
-    apex_available: ["com.android.tethering"],
-    lint: { strict_updatability_linting: true },
-}
-
 // Updatable tethering packaged for finalized API
 android_app {
     name: "Tethering",
diff --git a/Tethering/AndroidManifest_InProcess.xml b/Tethering/AndroidManifest_InProcess.xml
deleted file mode 100644
index b1f1240..0000000
--- a/Tethering/AndroidManifest_InProcess.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * 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.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.networkstack.tethering.inprocess"
-          android:sharedUserId="android.uid.system"
-          android:process="system">
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
-    <application>
-        <service android:name="com.android.networkstack.tethering.TetheringService"
-                 android:process="system"
-                 android:permission="android.permission.MAINLINE_NETWORK_STACK"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.net.ITetheringConnector.InProcess"/>
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index d84fef3..4506e5a 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -19,13 +19,6 @@
 }
 
 prebuilt_etc {
-    name: "TetheringInProcessFlag",
-    src: "in-process",
-    filename_from_src: true,
-    sub_dir: "flag",
-}
-
-prebuilt_etc {
     name: "TetheringOutOfProcessFlag",
     src: "out-of-process",
     filename_from_src: true,
@@ -254,27 +247,3 @@
     standalone_contents: ["service-connectivity"],
     apex_available: ["com.android.tethering"],
 }
-
-override_apex {
-    name: "com.android.tethering.inprocess",
-    base: "com.android.tethering",
-    package_name: "com.android.tethering.inprocess",
-    enabled: enable_tethering_next_apex,
-    bpfs: [
-        "block.o",
-        "clatd.o",
-        "dscpPolicy.o",
-        "netd.o",
-        "offload@inprocess.o",
-        "test@inprocess.o",
-    ],
-    apps: [
-        "ServiceConnectivityResources",
-        "InProcessTethering",
-    ],
-    prebuilts: [
-        "current_sdkinfo",
-        "privapp_allowlist_com.android.tethering",
-        "TetheringInProcessFlag",
-    ],
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index b6591a9..b0aa668 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -39,6 +39,8 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DeviceConfigUtils;
@@ -158,6 +160,8 @@
 
     public final int activeDataSubId;
 
+    private final Dependencies mDeps;
+
     private final boolean mEnableLegacyDhcpServer;
     private final int mOffloadPollInterval;
     // TODO: Add to TetheringConfigurationParcel if required.
@@ -170,7 +174,31 @@
     private final int mUsbTetheringFunction;
     protected final ContentResolver mContentResolver;
 
-    public TetheringConfiguration(Context ctx, SharedLog log, int id) {
+    /**
+     * A class wrapping dependencies of {@link TetheringConfiguration}, useful for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+                @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+            return DeviceConfigUtils.isFeatureEnabled(context, namespace, name,
+                    moduleName, defaultEnabled);
+        }
+
+        boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
+                boolean defaultValue) {
+            return DeviceConfig.getBoolean(namespace, name, defaultValue);
+        }
+    }
+
+    public TetheringConfiguration(@NonNull Context ctx, @NonNull SharedLog log, int id) {
+        this(ctx, log, id, new Dependencies());
+    }
+
+    @VisibleForTesting
+    public TetheringConfiguration(@NonNull Context ctx, @NonNull SharedLog log, int id,
+            @NonNull Dependencies deps) {
+        mDeps = deps;
         final SharedLog configLog = log.forSubComponent("config");
 
         activeDataSubId = id;
@@ -583,17 +611,7 @@
     }
 
     private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
-        // Due to the limitation of static mock for testing, using #getDeviceConfigProperty instead
-        // of DeviceConfig#getBoolean. If using #getBoolean here, the test can't know that the
-        // returned boolean value comes from device config or default value (because of null
-        // property string). See the test case testBpfOffload{*} in TetheringConfigurationTest.java.
-        final String value = getDeviceConfigProperty(name);
-        return value != null ? Boolean.parseBoolean(value) : defaultValue;
-    }
-
-    @VisibleForTesting
-    protected String getDeviceConfigProperty(String name) {
-        return DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, name);
+        return mDeps.getDeviceConfigBoolean(NAMESPACE_CONNECTIVITY, name, defaultValue);
     }
 
     /**
@@ -610,10 +628,9 @@
         return isFeatureEnabled(ctx, NAMESPACE_TETHERING, featureVersionFlag);
     }
 
-    @VisibleForTesting
-    protected boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
-        return DeviceConfigUtils.isFeatureEnabled(ctx, namespace, featureVersionFlag,
-                TETHERING_MODULE_NAME, false /* defaultEnabled */);
+    private boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
+        return mDeps.isFeatureEnabled(ctx, namespace, featureVersionFlag, TETHERING_MODULE_NAME,
+                false /* defaultEnabled */);
     }
 
     private Resources getResources(Context ctx, int subId) {
diff --git a/Tethering/tests/integration/base/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java
index ae39b24..1c0803e 100644
--- a/Tethering/tests/integration/base/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/base/android/net/TetheringTester.java
@@ -628,7 +628,7 @@
         return false;
     }
 
-    private void sendUploadPacket(ByteBuffer packet) throws Exception {
+    public void sendUploadPacket(ByteBuffer packet) throws Exception {
         mDownstreamReader.sendResponse(packet);
     }
 
@@ -680,4 +680,12 @@
 
         return verifyPacketNotNull("Download fail", getDownloadPacket(filter));
     }
+
+    // Send DHCPDISCOVER to DHCP server to see if DHCP server is still alive to handle
+    // the upcoming DHCP packets. This method should be only used when we know the DHCP
+    // server has been created successfully before.
+    public boolean testDhcpServerAlive(final MacAddress mac) throws Exception {
+        sendDhcpDiscover(mac.toByteArray());
+        return getNextDhcpPacket() != null;
+    }
 }
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 55854e2..21927df 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -39,6 +39,7 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -839,4 +840,41 @@
                 REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
                 tester, true /* isClat */);
     }
+
+    private static final byte[] ZeroLengthDhcpPacket = new byte[] {
+            // scapy.Ether(
+            //   dst="ff:ff:ff:ff:ff:ff")
+            // scapy.IP(
+            //   dst="255.255.255.255")
+            // scapy.UDP(sport=68, dport=67)
+            /* Ethernet Header */
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0xe0, (byte) 0x4f, (byte) 0x43, (byte) 0xe6, (byte) 0xfb, (byte) 0xd2,
+            (byte) 0x08, (byte) 0x00,
+            /* Ip header */
+            (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c, (byte) 0x00, (byte) 0x01,
+            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0xb6, (byte) 0x58,
+            (byte) 0x64, (byte) 0x4f, (byte) 0x60, (byte) 0x29, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff,
+            /* UDP header */
+            (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
+            (byte) 0x00, (byte) 0x08, (byte) 0x3a, (byte) 0xdf
+    };
+
+    @Test
+    public void testTetherZeroLengthDhcpPacket() throws Exception {
+        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+                toList(TEST_IP4_DNS));
+        tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+        // Send a zero-length DHCP packet to upstream DHCP server.
+        final ByteBuffer packet = ByteBuffer.wrap(ZeroLengthDhcpPacket);
+        tester.sendUploadPacket(packet);
+
+        // Send DHCPDISCOVER packet from another downstream tethered device to verify that upstream
+        // DHCP server has closed the listening socket and stopped reading, then we will not receive
+        // any DHCPOFFER in this case.
+        final MacAddress macAddress = MacAddress.fromString("11:22:33:44:55:66");
+        assertFalse(tester.testDhcpServerAlive(macAddress));
+    }
 }
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index b3fb3e4..81d4fbe 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -106,6 +106,7 @@
                 ConntrackMessage.Tuple tuple = ctmsg.tupleOrig;
 
                 if (nlmsghdr.nlmsg_type == (NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_NEW)
+                        && tuple != null
                         && tuple.protoNum == IPPROTO_TCP
                         && tuple.srcIp.equals(local.getAddress())
                         && tuple.dstIp.equals(remote.getAddress())
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
index 0d686ed..9e287a0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -19,22 +19,26 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import androidx.annotation.NonNull;
+
 import com.android.net.module.util.SharedLog;
 
 /** FakeTetheringConfiguration is used to override static method for testing. */
 public class FakeTetheringConfiguration extends TetheringConfiguration {
     FakeTetheringConfiguration(Context ctx, SharedLog log, int id) {
-        super(ctx, log, id);
-    }
+        super(ctx, log, id, new Dependencies() {
+            @Override
+            boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+                    @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+                return defaultEnabled;
+            }
 
-    @Override
-    protected String getDeviceConfigProperty(final String name) {
-        return null;
-    }
-
-    @Override
-    protected boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
-        return false;
+            @Override
+            boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
+                    boolean defaultValue) {
+                return defaultValue;
+            }
+        });
     }
 
     @Override
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index f662c02..3382af8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -21,14 +21,13 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.networkstack.tethering.TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER;
 import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
 import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION;
 import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION;
@@ -39,6 +38,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -49,12 +49,13 @@
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.PersistableBundle;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -73,8 +74,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -102,13 +102,13 @@
     @Mock private ModuleInfo mMi;
     private Context mMockContext;
     private boolean mHasTelephonyManager;
-    private MockitoSession mMockingSession;
     private MockContentResolver mContentResolver;
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
+    private final MockDependencies mDeps = new MockDependencies();
 
     private class MockTetheringConfiguration extends TetheringConfiguration {
         MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
-            super(ctx, log, id);
+            super(ctx, log, id, mDeps);
         }
 
         @Override
@@ -151,19 +151,43 @@
         }
     }
 
+    private static class MockDependencies extends TetheringConfiguration.Dependencies {
+        private ArrayMap<String, Boolean> mMockFlags = new ArrayMap<>();
+
+        @Override
+        boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+                @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+            return isMockFlagEnabled(name, defaultEnabled);
+        }
+
+        @Override
+        boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
+                boolean defaultValue) {
+            // Flags should use isFeatureEnabled instead of getBoolean; see comments in
+            // DeviceConfigUtils. getBoolean should only be used for the two legacy flags below.
+            assertTrue(OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD.equals(name)
+                    || TETHER_ENABLE_LEGACY_DHCP_SERVER.equals(name));
+
+            // Use the same mocking strategy as isFeatureEnabled for testing
+            return isMockFlagEnabled(name, defaultValue);
+        }
+
+        private boolean isMockFlagEnabled(@NonNull String name, boolean defaultEnabled) {
+            final Boolean flag = mMockFlags.getOrDefault(name, defaultEnabled);
+            // Value in the map can also be null
+            if (flag != null) return flag;
+            return defaultEnabled;
+        }
+
+        void setFeatureEnabled(@NonNull String flag, Boolean enabled) {
+            mMockFlags.put(flag, enabled);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
-        // TODO: use a dependencies class instead of mock statics.
-        mMockingSession = mockitoSession()
-                .initMocks(this)
-                .mockStatic(DeviceConfig.class)
-                .strictness(Strictness.WARN)
-                .startMocking();
-        DeviceConfigUtils.resetPackageVersionCacheForTest();
-        doReturn(null).when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
-        setTetherForceUpstreamAutomaticFlagVersion(null);
+        MockitoAnnotations.initMocks(this);
+        setTetherForceUpstreamAutomaticFlagEnabled(null);
 
         final PackageInfo pi = new PackageInfo();
         pi.setLongVersionCode(TEST_PACKAGE_VERSION);
@@ -202,7 +226,6 @@
 
     @After
     public void tearDown() throws Exception {
-        mMockingSession.finishMocking();
         DeviceConfigUtils.resetPackageVersionCacheForTest();
         // Call {@link #clearSettingsProvider()} before and after using FakeSettingsProvider.
         FakeSettingsProvider.clearSettingsProvider();
@@ -211,7 +234,7 @@
     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
         when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
                 legacyTetherUpstreamTypes);
-        return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
     }
 
     @Test
@@ -297,7 +320,7 @@
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
         assertTrue(upstreamIterator.hasNext());
         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
@@ -320,7 +343,7 @@
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
         assertTrue(upstreamIterator.hasNext());
         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
@@ -338,7 +361,7 @@
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
         assertTrue(upstreamIterator.hasNext());
         assertEquals(TYPE_WIFI, upstreamIterator.next().intValue());
@@ -350,27 +373,26 @@
     }
 
     private void initializeBpfOffloadConfiguration(
-            final boolean fromRes, final String fromDevConfig) {
+            final boolean fromRes, final Boolean fromDevConfig) {
         when(mResources.getBoolean(R.bool.config_tether_enable_bpf_offload)).thenReturn(fromRes);
-        doReturn(fromDevConfig).when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD)));
+        mDeps.setFeatureEnabled(
+                TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD, fromDevConfig);
     }
 
     @Test
     public void testBpfOffloadEnabledByResource() {
         initializeBpfOffloadConfiguration(true, null /* unset */);
         final TetheringConfiguration enableByRes =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(enableByRes.isBpfOffloadEnabled());
     }
 
     @Test
     public void testBpfOffloadEnabledByDeviceConfigOverride() {
         for (boolean res : new boolean[]{true, false}) {
-            initializeBpfOffloadConfiguration(res, "true");
+            initializeBpfOffloadConfiguration(res, true);
             final TetheringConfiguration enableByDevConOverride =
-                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
             assertTrue(enableByDevConOverride.isBpfOffloadEnabled());
         }
     }
@@ -379,16 +401,16 @@
     public void testBpfOffloadDisabledByResource() {
         initializeBpfOffloadConfiguration(false, null /* unset */);
         final TetheringConfiguration disableByRes =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertFalse(disableByRes.isBpfOffloadEnabled());
     }
 
     @Test
     public void testBpfOffloadDisabledByDeviceConfigOverride() {
         for (boolean res : new boolean[]{true, false}) {
-            initializeBpfOffloadConfiguration(res, "false");
+            initializeBpfOffloadConfiguration(res, false);
             final TetheringConfiguration disableByDevConOverride =
-                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
             assertFalse(disableByDevConOverride.isBpfOffloadEnabled());
         }
     }
@@ -397,22 +419,18 @@
     public void testNewDhcpServerDisabled() {
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 true);
-        doReturn("false").when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER, false);
 
         final TetheringConfiguration enableByRes =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(enableByRes.useLegacyDhcpServer());
 
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
-        doReturn("true").when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER, true);
 
         final TetheringConfiguration enableByDevConfig =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(enableByDevConfig.useLegacyDhcpServer());
     }
 
@@ -420,12 +438,10 @@
     public void testNewDhcpServerEnabled() {
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
-        doReturn("false").when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER, false);
 
         final TetheringConfiguration cfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         assertFalse(cfg.useLegacyDhcpServer());
     }
@@ -433,7 +449,7 @@
     @Test
     public void testOffloadIntervalByResource() {
         final TetheringConfiguration intervalByDefault =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS,
                 intervalByDefault.getOffloadPollInterval());
 
@@ -442,7 +458,7 @@
             when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn(
                     override);
             final TetheringConfiguration overrideByRes =
-                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
             assertEquals(override, overrideByRes.getOffloadPollInterval());
         }
     }
@@ -451,7 +467,7 @@
     public void testGetResourcesBySubId() {
         setUpResourceForSubId();
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(cfg.provisioningApp.length == 0);
         final int anyValidSubId = 1;
         final MockTetheringConfiguration mockCfg =
@@ -493,7 +509,7 @@
         mockService(Context.CARRIER_CONFIG_SERVICE,
                 CarrierConfigManager.class, null);
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         assertTrue(cfg.isCarrierSupportTethering);
         assertTrue(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
@@ -506,7 +522,7 @@
                 CarrierConfigManager.class, mCarrierConfigManager);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         assertTrue(cfg.isCarrierSupportTethering);
         assertTrue(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
@@ -521,7 +537,7 @@
         mCarrierConfig.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         if (SdkLevel.isAtLeastT()) {
             assertFalse(cfg.isCarrierSupportTethering);
@@ -535,13 +551,13 @@
     @Test
     public void testEnableLegacyWifiP2PAddress() throws Exception {
         final TetheringConfiguration defaultCfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertFalse(defaultCfg.shouldEnableWifiP2pDedicatedIp());
 
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip))
                 .thenReturn(true);
         final TetheringConfiguration testCfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(testCfg.shouldEnableWifiP2pDedicatedIp());
     }
 
@@ -576,16 +592,13 @@
     public void testChooseUpstreamAutomatically_FlagOverride() throws Exception {
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mMockContext, NAMESPACE_CONNECTIVITY,
-                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, APEX_NAME, false));
-
+        setTetherForceUpstreamAutomaticFlagEnabled(true);
         assertChooseUpstreamAutomaticallyIs(true);
 
-        setTetherForceUpstreamAutomaticFlagVersion(0L);
+        setTetherForceUpstreamAutomaticFlagEnabled(null);
         assertChooseUpstreamAutomaticallyIs(false);
 
-        setTetherForceUpstreamAutomaticFlagVersion(Long.MAX_VALUE);
+        setTetherForceUpstreamAutomaticFlagEnabled(false);
         assertChooseUpstreamAutomaticallyIs(false);
     }
 
@@ -593,7 +606,7 @@
     public void testChooseUpstreamAutomatically_FlagOverrideOnSAndT() throws Exception {
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
+        setTetherForceUpstreamAutomaticFlagEnabled(true);
         assertChooseUpstreamAutomaticallyIs(false);
     }
 
@@ -604,28 +617,24 @@
         // TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION is.
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mMockContext, NAMESPACE_CONNECTIVITY,
-                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, APEX_NAME, false));
-
+        setTetherForceUpstreamAutomaticFlagEnabled(true);
         assertChooseUpstreamAutomaticallyIs(true);
 
-        setTetherForceUpstreamAutomaticFlagVersion(0L);
+        setTetherForceUpstreamAutomaticFlagEnabled(null);
         assertChooseUpstreamAutomaticallyIs(true);
 
-        setTetherForceUpstreamAutomaticFlagVersion(Long.MAX_VALUE);
+        setTetherForceUpstreamAutomaticFlagEnabled(false);
         assertChooseUpstreamAutomaticallyIs(true);
     }
 
-    private void setTetherForceUpstreamAutomaticFlagVersion(Long version) {
-        doReturn(version == null ? null : Long.toString(version)).when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                        eq(TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION)));
+    private void setTetherForceUpstreamAutomaticFlagEnabled(Boolean enabled) {
+        mDeps.setFeatureEnabled(
+                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, enabled);
     }
 
     private void assertChooseUpstreamAutomaticallyIs(boolean value) {
-        assertEquals(value, new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)
-                .chooseUpstreamAutomatically);
+        assertEquals(value, new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).chooseUpstreamAutomatically);
     }
 
     @Test
@@ -654,7 +663,7 @@
 
     private void assertIsUsingNcm(boolean expected) {
         final TetheringConfiguration cfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(expected, cfg.isUsingNcm());
     }
 
@@ -704,7 +713,7 @@
 
     private void assertUsbAndNcmRegexs(final String[] usbRegexs, final String[] ncmRegexs) {
         final TetheringConfiguration cfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertArrayEquals(usbRegexs, cfg.tetherableUsbRegexs);
         assertArrayEquals(ncmRegexs, cfg.tetherableNcmRegexs);
     }
@@ -716,28 +725,28 @@
 
         final int defaultSubnetPrefixLength = 0;
         final TetheringConfiguration defaultCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(defaultSubnetPrefixLength, defaultCfg.getP2pLeasesSubnetPrefixLength());
 
         final int prefixLengthTooSmall = -1;
         when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
                 prefixLengthTooSmall);
         final TetheringConfiguration tooSmallCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(defaultSubnetPrefixLength, tooSmallCfg.getP2pLeasesSubnetPrefixLength());
 
         final int prefixLengthTooLarge = 31;
         when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
                 prefixLengthTooLarge);
         final TetheringConfiguration tooLargeCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(defaultSubnetPrefixLength, tooLargeCfg.getP2pLeasesSubnetPrefixLength());
 
         final int p2pLeasesSubnetPrefixLength = 27;
         when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
                 p2pLeasesSubnetPrefixLength);
         final TetheringConfiguration p2pCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(p2pLeasesSubnetPrefixLength, p2pCfg.getP2pLeasesSubnetPrefixLength());
     }
 }
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 229dce3..b3f8ed6 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -103,18 +103,6 @@
 }
 
 bpf {
-    name: "offload@inprocess.o",
-    srcs: ["offload@inprocess.c"],
-    btf: true,
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-DBTF",
-        "-DINPROCESS",
-    ],
-}
-
-bpf {
     name: "test.o",
     srcs: ["test.c"],
     cflags: [
@@ -135,18 +123,6 @@
 }
 
 bpf {
-    name: "test@inprocess.o",
-    srcs: ["test@inprocess.c"],
-    btf: true,
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-DBTF",
-        "-DINPROCESS",
-    ],
-}
-
-bpf {
     name: "clatd.o",
     srcs: ["clatd.c"],
     btf: true,
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index f2a3e62..3797a38 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,8 +19,8 @@
 #include <netinet/in.h>
 #include <stdint.h>
 
-// The resulting .o needs to load on the Android T beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include "bpf_helpers.h"
 
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index f05b93e..85ba58e 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,8 +30,8 @@
 #define __kernel_udphdr udphdr
 #include <linux/udp.h>
 
-// The resulting .o needs to load on the Android T beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index 72f63c6..262b65b 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -27,8 +27,8 @@
 #include <stdint.h>
 #include <string.h>
 
-// The resulting .o needs to load on the Android T beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include "bpf_helpers.h"
 #include "dscpPolicy.h"
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 39dff7f..839ca40 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-// The resulting .o needs to load on the Android T Beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include <bpf_helpers.h>
 #include <linux/bpf.h>
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index f4d4254..80d1a41 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -38,13 +38,7 @@
 // Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
 #define TETHERING_UID AID_ROOT
 
-#ifdef INPROCESS
-#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define TETHERING_GID AID_SYSTEM
-#else
 #define TETHERING_GID AID_NETWORK_STACK
-#endif
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
diff --git a/bpf_progs/offload@inprocess.c b/bpf_progs/offload@inprocess.c
deleted file mode 120000
index 4092e0d..0000000
--- a/bpf_progs/offload@inprocess.c
+++ /dev/null
@@ -1 +0,0 @@
-offload.c
\ No newline at end of file
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index d1f780f..091743c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -32,13 +32,7 @@
 // Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
 #define TETHERING_UID AID_ROOT
 
-#ifdef INPROCESS
-#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define TETHERING_GID AID_SYSTEM
-#else
 #define TETHERING_GID AID_NETWORK_STACK
-#endif
 
 // This is non production code, only used for testing
 // Needed because the bitmap array definition is non-kosher for pre-T OS devices.
diff --git a/bpf_progs/test@inprocess.c b/bpf_progs/test@inprocess.c
deleted file mode 120000
index aeebb26..0000000
--- a/bpf_progs/test@inprocess.c
+++ /dev/null
@@ -1 +0,0 @@
-test.c
\ No newline at end of file
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 381a18a..2315521 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -2535,6 +2535,26 @@
     }
 
     /**
+     * Get the supported keepalive count for each transport configured in resource overlays.
+     *
+     * @return An array of supported keepalive count for each transport type.
+     * @hide
+     */
+    @RequiresPermission(anyOf = { android.Manifest.permission.NETWORK_SETTINGS,
+            // CTS 13 used QUERY_ALL_PACKAGES to get the resource value, which was implemented
+            // as below in KeepaliveUtils. Also allow that permission so that KeepaliveUtils can
+            // use this method and avoid breaking released CTS. Apps that have this permission
+            // can query the resource themselves anyway.
+            android.Manifest.permission.QUERY_ALL_PACKAGES })
+    public int[] getSupportedKeepalives() {
+        try {
+            return mService.getSupportedKeepalives();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface. An attempt to add a route that
      * already exists is ignored, but treated as successful.
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 1372e9a..ebe8bca 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -195,6 +195,8 @@
 
     void stopKeepalive(in ISocketKeepaliveCallback cb);
 
+    int[] getSupportedKeepalives();
+
     String getCaptivePortalServerUrl();
 
     byte[] getNetworkWatchlistConfigHash();
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 3cc9c65..92e9599 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -1348,6 +1348,18 @@
     }
 
     /**
+     * Gets the transports as an int. Internal callers only.
+     *
+     * Prefer getTransportTypes/hasTransportType if not immediately collapsing back into a scalar.
+     *
+     * @return a long integer representing the transport types.
+     * @hide
+     */
+    public long getTransportTypesInternal() {
+        return mTransportTypes;
+    }
+
+    /**
      * Sets all the transports set on this {@code NetworkCapability} instance.
      * This overwrites any existing transports.
      *
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 64f14a1..fae2499 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -19,9 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -36,8 +33,6 @@
  */
 @SystemApi
 public final class ApfCapabilities implements Parcelable {
-    private static ConnectivityResources sResources;
-
     /**
      * Version of APF instruction set supported for packet filtering. 0 indicates no support for
      * packet filtering using APF programs.
@@ -67,15 +62,6 @@
         apfPacketFormat = in.readInt();
     }
 
-    @NonNull
-    private static synchronized ConnectivityResources getResources(@NonNull Context ctx) {
-        if (sResources == null)  {
-            sResources = new ConnectivityResources(ctx);
-        }
-        return sResources;
-    }
-
-
     @Override
     public int describeContents() {
         return 0;
diff --git a/framework/src/android/net/util/KeepaliveUtils.java b/framework/src/android/net/util/KeepaliveUtils.java
index 8d7a0b3..07b8c45 100644
--- a/framework/src/android/net/util/KeepaliveUtils.java
+++ b/framework/src/android/net/util/KeepaliveUtils.java
@@ -18,11 +18,8 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityResources;
+import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-import android.text.TextUtils;
-import android.util.AndroidRuntimeException;
 
 /**
  * Collection of utilities for socket keepalive offload.
@@ -33,64 +30,20 @@
 
     public static final String TAG = "KeepaliveUtils";
 
-    public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
-        public KeepaliveDeviceConfigurationException(final String msg) {
-            super(msg);
-        }
-    }
-
     /**
      * Read supported keepalive count for each transport type from overlay resource. This should be
      * used to create a local variable store of resource customization, and use it as the input for
-     * {@link getSupportedKeepalivesForNetworkCapabilities}.
+     * {@link #getSupportedKeepalivesForNetworkCapabilities}.
      *
      * @param context The context to read resource from.
      * @return An array of supported keepalive count for each transport type.
+     * @deprecated This is used by CTS 13, but can be removed after switching it to
+     * {@link ConnectivityManager#getSupportedKeepalives()}.
      */
     @NonNull
+    @Deprecated
     public static int[] getSupportedKeepalives(@NonNull Context context) {
-        String[] res = null;
-        try {
-            final ConnectivityResources connRes = new ConnectivityResources(context);
-            // TODO: use R.id.config_networkSupportedKeepaliveCount directly
-            final int id = connRes.get().getIdentifier("config_networkSupportedKeepaliveCount",
-                    "array", connRes.getResourcesContext().getPackageName());
-            res = new ConnectivityResources(context).get().getStringArray(id);
-        } catch (Resources.NotFoundException unused) {
-        }
-        if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
-
-        final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
-        for (final String row : res) {
-            if (TextUtils.isEmpty(row)) {
-                throw new KeepaliveDeviceConfigurationException("Empty string");
-            }
-            final String[] arr = row.split(",");
-            if (arr.length != 2) {
-                throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
-            }
-
-            int transport;
-            int supported;
-            try {
-                transport = Integer.parseInt(arr[0]);
-                supported = Integer.parseInt(arr[1]);
-            } catch (NumberFormatException e) {
-                throw new KeepaliveDeviceConfigurationException("Invalid number format");
-            }
-
-            if (!NetworkCapabilities.isValidTransport(transport)) {
-                throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
-            }
-
-            if (supported < 0) {
-                throw new KeepaliveDeviceConfigurationException(
-                        "Invalid supported count " + supported + " for "
-                                + NetworkCapabilities.transportNameOf(transport));
-            }
-            ret[transport] = supported;
-        }
-        return ret;
+        return context.getSystemService(ConnectivityManager.class).getSupportedKeepalives();
     }
 
     /**
diff --git a/nearby/tests/multidevices/README.md b/nearby/tests/multidevices/README.md
index b64667c..9d086de 100644
--- a/nearby/tests/multidevices/README.md
+++ b/nearby/tests/multidevices/README.md
@@ -43,14 +43,24 @@
 *   Adjust Bluetooth profile configurations. \
     The Fast Pair provider simulator is an opposite role to the seeker. It needs
     to enable/disable the following Bluetooth profile:
-    *   Disable A2DP (profile_supported_a2dp)
-    *   Disable the AVRCP controller (profile_supported_avrcp_controller)
-    *   Enable A2DP sink (profile_supported_a2dp_sink)
-    *   Enable the HFP client connection service (profile_supported_hfpclient,
-        hfp_client_connection_service_enabled)
-    *   Enable the AVRCP target (profile_supported_avrcp_target)
-    *   Enable the automatic audio focus request
-        (a2dp_sink_automatically_request_audio_focus)
+    *   Disable A2DP source (bluetooth.profile.a2dp.source.enabled)
+    *   Enable A2DP sink (bluetooth.profile.a2dp.sink.enabled)
+    *   Disable the AVRCP controller (bluetooth.profile.avrcp.controller.enabled)
+    *   Enable the AVRCP target (bluetooth.profile.avrcp.target.enabled)
+    *   Enable the HFP service (bluetooth.profile.hfp.ag.enabled, bluetooth.profile.hfp.hf.enabled)
+
+```makefile
+# The Bluetooth profiles that Fast Pair provider simulator expect to have enabled.
+PRODUCT_PRODUCT_PROPERTIES += \
+    bluetooth.device.default_name=FastPairProviderSimulator \
+    bluetooth.profile.a2dp.source.enabled=false \
+    bluetooth.profile.a2dp.sink.enabled=true \
+    bluetooth.profile.avrcp.controller.enabled=false \
+    bluetooth.profile.avrcp.target.enabled=true \
+    bluetooth.profile.hfp.ag.enabled=true \
+    bluetooth.profile.hfp.hf.enabled=true
+```
+
 *   Adjust Bluetooth TX power limitation in Bluetooth module and disable the
     Fast Pair in Google Play service (aka GMS)
 
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 383ed2c..f30f120 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -19,10 +19,10 @@
 import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
 import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
-import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
+import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -61,6 +61,7 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.InetAddressUtils;
 import com.android.net.module.util.PermissionUtils;
 import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.ExecutorProvider;
@@ -72,6 +73,7 @@
 import com.android.server.connectivity.mdns.MdnsServiceInfo;
 import com.android.server.connectivity.mdns.MdnsSocketClientBase;
 import com.android.server.connectivity.mdns.MdnsSocketProvider;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -80,11 +82,6 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -107,8 +104,6 @@
      */
     private static final String MDNS_DISCOVERY_MANAGER_VERSION = "mdns_discovery_manager_version";
     private static final String LOCAL_DOMAIN_NAME = "local";
-    // Max label length as per RFC 1034/1035
-    private static final int MAX_LABEL_LENGTH = 63;
 
     /**
      * Enable advertising using the Java MdnsAdvertiser, instead of the legacy mdnsresponder
@@ -569,18 +564,7 @@
              */
             @NonNull
             private String truncateServiceName(@NonNull String originalName) {
-                // UTF-8 is at most 4 bytes per character; return early in the common case where
-                // the name can't possibly be over the limit given its string length.
-                if (originalName.length() <= MAX_LABEL_LENGTH / 4) return originalName;
-
-                final Charset utf8 = StandardCharsets.UTF_8;
-                final CharsetEncoder encoder = utf8.newEncoder();
-                final ByteBuffer out = ByteBuffer.allocate(MAX_LABEL_LENGTH);
-                // encode will write as many characters as possible to the out buffer, and just
-                // return an overflow code if there were too many characters (no need to check the
-                // return code here, this method truncates the name on purpose).
-                encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
-                return new String(out.array(), 0, out.position(), utf8);
+                return MdnsUtils.truncateServiceName(originalName, MAX_LABEL_LENGTH);
             }
 
             private void stopDiscoveryManagerRequest(ClientRequest request, int clientId, int id,
@@ -1240,16 +1224,10 @@
         }
         for (String ipv6Address : v6Addrs) {
             try {
-                final InetAddress addr = InetAddresses.parseNumericAddress(ipv6Address);
-                if (addr.isLinkLocalAddress()) {
-                    final Inet6Address v6Addr = Inet6Address.getByAddress(
-                            null /* host */, addr.getAddress(),
-                            serviceInfo.getInterfaceIndex());
-                    addresses.add(v6Addr);
-                } else {
-                    addresses.add(addr);
-                }
-            } catch (IllegalArgumentException | UnknownHostException e) {
+                final Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(
+                        ipv6Address);
+                addresses.add(InetAddressUtils.withScopeId(addr, serviceInfo.getInterfaceIndex()));
+            } catch (IllegalArgumentException e) {
                 Log.wtf(TAG, "Invalid ipv6 address", e);
             }
         }
@@ -1373,8 +1351,8 @@
          * @return true if the MdnsDiscoveryManager feature is enabled.
          */
         public boolean isMdnsDiscoveryManagerEnabled(Context context) {
-            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context,
-                    NAMESPACE_CONNECTIVITY, MDNS_DISCOVERY_MANAGER_VERSION,
+            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    MDNS_DISCOVERY_MANAGER_VERSION, DeviceConfigUtils.TETHERING_MODULE_NAME,
                     false /* defaultEnabled */);
         }
 
@@ -1385,8 +1363,9 @@
          * @return true if the MdnsAdvertiser feature is enabled.
          */
         public boolean isMdnsAdvertiserEnabled(Context context) {
-            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context,
-                    NAMESPACE_CONNECTIVITY, MDNS_ADVERTISER_VERSION, false /* defaultEnabled */);
+            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    MDNS_ADVERTISER_VERSION, DeviceConfigUtils.TETHERING_MODULE_NAME,
+                    false /* defaultEnabled */);
         }
 
         /**
diff --git a/service-t/src/com/android/server/connectivity/mdns/ISocketNetLinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/AbstractSocketNetlink.java
similarity index 95%
rename from service-t/src/com/android/server/connectivity/mdns/ISocketNetLinkMonitor.java
rename to service-t/src/com/android/server/connectivity/mdns/AbstractSocketNetlink.java
index ef3928c..b792e46 100644
--- a/service-t/src/com/android/server/connectivity/mdns/ISocketNetLinkMonitor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/AbstractSocketNetlink.java
@@ -19,7 +19,7 @@
 /**
  * The interface for netlink monitor.
  */
-public interface ISocketNetLinkMonitor {
+public interface AbstractSocketNetlink {
 
     /**
      * Returns if the netlink monitor is supported or not. By default, it is not supported.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 33fef9d..a332da7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkAddress;
@@ -29,6 +31,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -359,7 +362,7 @@
             // "Name (2)", then "Name (3)" etc.
             // TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t
             final NsdServiceInfo newInfo = new NsdServiceInfo();
-            newInfo.setServiceName(mOriginalName + " (" + (mConflictCount + renameCount + 1) + ")");
+            newInfo.setServiceName(getUpdatedServiceName(renameCount));
             newInfo.setServiceType(mServiceInfo.getServiceType());
             for (Map.Entry<String, byte[]> attr : mServiceInfo.getAttributes().entrySet()) {
                 newInfo.setAttribute(attr.getKey(),
@@ -372,6 +375,13 @@
             return newInfo;
         }
 
+        private String getUpdatedServiceName(int renameCount) {
+            final String suffix = " (" + (mConflictCount + renameCount + 1) + ")";
+            final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalName,
+                    MAX_LABEL_LENGTH - suffix.length());
+            return truncatedServiceName + suffix;
+        }
+
         @NonNull
         public NsdServiceInfo getServiceInfo() {
             return mServiceInfo;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 491698d..64985ad 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -16,7 +16,7 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.MdnsSocketProvider.isNetworkMatched;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 31627f8..119c7a8 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -78,7 +78,7 @@
         }
 
         mPacketReader = new MulticastPacketReader(networkInterface.getName(), mFileDescriptor,
-                new Handler(looper), packetReadBuffer, port);
+                new Handler(looper), packetReadBuffer);
         mPacketReader.start();
     }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 7af2231..4504bb6 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -16,8 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.MdnsSocketProvider.ensureRunningOnHandlerThread;
-import static com.android.server.connectivity.mdns.MdnsSocketProvider.isNetworkMatched;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
index bcee9d1..19630e3 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -46,6 +46,8 @@
 
     public static final long RECEIPT_TIME_NOT_SENT = 0L;
     public static final int CLASS_ANY = 0x00ff;
+    /** Max label length as per RFC 1034/1035 */
+    public static final int MAX_LABEL_LENGTH = 63;
 
     /** Status indicating that the record is current. */
     public static final int STATUS_OK = 0;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index f1389ca..8bc598d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -16,7 +16,7 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.MdnsSocketProvider.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
 
 import android.annotation.NonNull;
 import android.os.Handler;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
new file mode 100644
index 0000000..cd0be67
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -0,0 +1,178 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The {@link MdnsServiceCache} manages the service which discovers from each socket and cache these
+ * services to reduce duplicated queries.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the looper thread.
+ *  However, the constructor is an exception, as it is called on another thread;
+ *  therefore for thread safety all members of this class MUST either be final or initialized
+ *  to their default value (0, false or null).
+ */
+public class MdnsServiceCache {
+    private static class CacheKey {
+        @NonNull final String mLowercaseServiceType;
+        @Nullable final Network mNetwork;
+
+        CacheKey(@NonNull String serviceType, @Nullable Network network) {
+            mLowercaseServiceType = toDnsLowerCase(serviceType);
+            mNetwork = network;
+        }
+
+        @Override public int hashCode() {
+            return Objects.hash(mLowercaseServiceType, mNetwork);
+        }
+
+        @Override public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof CacheKey)) {
+                return false;
+            }
+            return Objects.equals(mLowercaseServiceType, ((CacheKey) other).mLowercaseServiceType)
+                    && Objects.equals(mNetwork, ((CacheKey) other).mNetwork);
+        }
+    }
+    /**
+     * A map of cached services. Key is composed of service name, type and network. Value is the
+     * service which use the service type to discover from each socket.
+     */
+    @NonNull
+    private final ArrayMap<CacheKey, List<MdnsResponse>> mCachedServices = new ArrayMap<>();
+    @NonNull
+    private final Handler mHandler;
+
+    public MdnsServiceCache(@NonNull Looper looper) {
+        mHandler = new Handler(looper);
+    }
+
+    /**
+     * Get the cache services which are queried from given service type and network.
+     *
+     * @param serviceType the target service type.
+     * @param network the target network
+     * @return the set of services which matches the given service type.
+     */
+    @NonNull
+    public List<MdnsResponse> getCachedServices(@NonNull String serviceType,
+            @Nullable Network network) {
+        ensureRunningOnHandlerThread(mHandler);
+        final CacheKey key = new CacheKey(serviceType, network);
+        return mCachedServices.containsKey(key)
+                ? Collections.unmodifiableList(new ArrayList<>(mCachedServices.get(key)))
+                : Collections.emptyList();
+    }
+
+    private MdnsResponse findMatchedResponse(@NonNull List<MdnsResponse> responses,
+            @NonNull String serviceName) {
+        for (MdnsResponse response : responses) {
+            if (equalsIgnoreDnsCase(serviceName, response.getServiceInstanceName())) {
+                return response;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the cache service.
+     *
+     * @param serviceName the target service name.
+     * @param serviceType the target service type.
+     * @param network the target network
+     * @return the service which matches given conditions.
+     */
+    @Nullable
+    public MdnsResponse getCachedService(@NonNull String serviceName,
+            @NonNull String serviceType, @Nullable Network network) {
+        ensureRunningOnHandlerThread(mHandler);
+        final List<MdnsResponse> responses =
+                mCachedServices.get(new CacheKey(serviceType, network));
+        if (responses == null) {
+            return null;
+        }
+        final MdnsResponse response = findMatchedResponse(responses, serviceName);
+        return response != null ? new MdnsResponse(response) : null;
+    }
+
+    /**
+     * Add or update a service.
+     *
+     * @param serviceType the service type.
+     * @param network the target network
+     * @param response the response of the discovered service.
+     */
+    public void addOrUpdateService(@NonNull String serviceType, @Nullable Network network,
+            @NonNull MdnsResponse response) {
+        ensureRunningOnHandlerThread(mHandler);
+        final List<MdnsResponse> responses = mCachedServices.computeIfAbsent(
+                new CacheKey(serviceType, network), key -> new ArrayList<>());
+        // Remove existing service if present.
+        final MdnsResponse existing =
+                findMatchedResponse(responses, response.getServiceInstanceName());
+        responses.remove(existing);
+        responses.add(response);
+    }
+
+    /**
+     * Remove a service which matches the given service name, type and network.
+     *
+     * @param serviceName the target service name.
+     * @param serviceType the target service type.
+     * @param network the target network.
+     */
+    @Nullable
+    public MdnsResponse removeService(@NonNull String serviceName, @NonNull String serviceType,
+            @Nullable Network network) {
+        ensureRunningOnHandlerThread(mHandler);
+        final List<MdnsResponse> responses =
+                mCachedServices.get(new CacheKey(serviceType, network));
+        if (responses == null) {
+            return null;
+        }
+        final Iterator<MdnsResponse> iterator = responses.iterator();
+        while (iterator.hasNext()) {
+            final MdnsResponse response = iterator.next();
+            if (equalsIgnoreDnsCase(serviceName, response.getServiceInstanceName())) {
+                iterator.remove();
+                return response;
+            }
+        }
+        return null;
+    }
+
+    // TODO: check ttl expiration for each service and notify to the clients.
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index c45345a..d090a4d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -20,6 +20,9 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -74,7 +77,7 @@
     @NonNull private final Dependencies mDependencies;
     @NonNull private final NetworkCallback mNetworkCallback;
     @NonNull private final TetheringEventCallback mTetheringEventCallback;
-    @NonNull private final ISocketNetLinkMonitor mSocketNetlinkMonitor;
+    @NonNull private final AbstractSocketNetlink mSocketNetlinkMonitor;
     private final ArrayMap<Network, SocketInfo> mNetworkSockets = new ArrayMap<>();
     private final ArrayMap<String, SocketInfo> mTetherInterfaceSockets = new ArrayMap<>();
     private final ArrayMap<Network, LinkProperties> mActiveNetworksLinkProperties =
@@ -171,7 +174,7 @@
             return iface.getIndex();
         }
         /*** Creates a SocketNetlinkMonitor */
-        public ISocketNetLinkMonitor createSocketNetlinkMonitor(@NonNull final Handler handler,
+        public AbstractSocketNetlink createSocketNetlinkMonitor(@NonNull final Handler handler,
                 @NonNull final SharedLog log,
                 @NonNull final NetLinkMonitorCallBack cb) {
             return SocketNetLinkMonitorFactory.createNetLinkMonitor(handler, log, cb);
@@ -242,14 +245,6 @@
         }
     }
 
-    /*** Ensure that current running thread is same as given handler thread */
-    public static void ensureRunningOnHandlerThread(Handler handler) {
-        if (handler.getLooper().getThread() != Thread.currentThread()) {
-            throw new IllegalStateException(
-                    "Not running on Handler thread: " + Thread.currentThread().getName());
-        }
-    }
-
     /*** Start monitoring sockets by listening callbacks for sockets creation or removal */
     public void startMonitoringSockets() {
         ensureRunningOnHandlerThread(mHandler);
@@ -317,12 +312,6 @@
         maybeStopMonitoringSockets();
     }
 
-    /*** Check whether the target network is matched current network */
-    public static boolean isNetworkMatched(@Nullable Network targetNetwork,
-            @Nullable Network currentNetwork) {
-        return targetNetwork == null || targetNetwork.equals(currentNetwork);
-    }
-
     private boolean matchRequestedNetwork(Network network) {
         return hasAllNetworksRequest()
                 || mCallbacksToRequestedNetworks.containsValue(network);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
index 078c4dd..63119ac 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
@@ -16,7 +16,7 @@
 
 package com.android.server.connectivity.mdns;
 
-import static com.android.server.connectivity.mdns.MdnsSocketProvider.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
 
 import android.annotation.NonNull;
 import android.os.Handler;
@@ -63,8 +63,10 @@
      */
     protected MulticastPacketReader(@NonNull String interfaceTag,
             @NonNull ParcelFileDescriptor socket, @NonNull Handler handler,
-            @NonNull byte[] buffer, int port) {
-        super(handler, new RecvBuffer(buffer, new InetSocketAddress(port)));
+            @NonNull byte[] buffer) {
+        // Set the port to zero as placeholder as the recvfrom() call will fill the actual port
+        // value later.
+        super(handler, new RecvBuffer(buffer, new InetSocketAddress(0 /* port */)));
         mLogTag = MulticastPacketReader.class.getSimpleName() + "/" + interfaceTag;
         mSocket = socket;
         mHandler = handler;
diff --git a/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java b/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
index 4650255..6bc7941 100644
--- a/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
+++ b/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
@@ -30,7 +30,7 @@
     /**
      * Creates a new netlink monitor.
      */
-    public static ISocketNetLinkMonitor createNetLinkMonitor(@NonNull final Handler handler,
+    public static AbstractSocketNetlink createNetLinkMonitor(@NonNull final Handler handler,
             @NonNull SharedLog log, @NonNull MdnsSocketProvider.NetLinkMonitorCallBack cb) {
         return new SocketNetlinkMonitor(handler, log, cb);
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
index 6395b53..40191cf 100644
--- a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
@@ -28,13 +28,13 @@
 import com.android.net.module.util.netlink.NetlinkMessage;
 import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
 import com.android.net.module.util.netlink.StructIfaddrMsg;
-import com.android.server.connectivity.mdns.ISocketNetLinkMonitor;
+import com.android.server.connectivity.mdns.AbstractSocketNetlink;
 import com.android.server.connectivity.mdns.MdnsSocketProvider;
 
 /**
  * The netlink monitor for MdnsSocketProvider.
  */
-public class SocketNetlinkMonitor extends NetlinkMonitor implements ISocketNetLinkMonitor {
+public class SocketNetlinkMonitor extends NetlinkMonitor implements AbstractSocketNetlink {
 
     public static final String TAG = SocketNetlinkMonitor.class.getSimpleName();
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
new file mode 100644
index 0000000..5cc789f
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.server.connectivity.mdns.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Handler;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Mdns utility functions.
+ */
+public class MdnsUtils {
+
+    private MdnsUtils() { }
+
+    /**
+     * Convert the string to DNS case-insensitive lowercase
+     *
+     * Per rfc6762#page-46, accented characters are not defined to be automatically equivalent to
+     * their unaccented counterparts. So the "DNS lowercase" should be if character is A-Z then they
+     * transform into a-z. Otherwise, they are kept as-is.
+     */
+    public static String toDnsLowerCase(@NonNull String string) {
+        final char[] outChars = new char[string.length()];
+        for (int i = 0; i < string.length(); i++) {
+            outChars[i] = toDnsLowerCase(string.charAt(i));
+        }
+        return new String(outChars);
+    }
+
+    /**
+     * Compare two strings by DNS case-insensitive lowercase.
+     */
+    public static boolean equalsIgnoreDnsCase(@NonNull String a, @NonNull String b) {
+        if (a.length() != b.length()) return false;
+        for (int i = 0; i < a.length(); i++) {
+            if (toDnsLowerCase(a.charAt(i)) != toDnsLowerCase(b.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static char toDnsLowerCase(char a) {
+        return a >= 'A' && a <= 'Z' ? (char) (a + ('a' - 'A')) : a;
+    }
+
+    /*** Ensure that current running thread is same as given handler thread */
+    public static void ensureRunningOnHandlerThread(@NonNull Handler handler) {
+        if (handler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on Handler thread: " + Thread.currentThread().getName());
+        }
+    }
+
+    /*** Check whether the target network is matched current network */
+    public static boolean isNetworkMatched(@Nullable Network targetNetwork,
+            @Nullable Network currentNetwork) {
+        return targetNetwork == null || targetNetwork.equals(currentNetwork);
+    }
+
+    /**
+     * Truncate a service name to up to maxLength UTF-8 bytes.
+     */
+    public static String truncateServiceName(@NonNull String originalName, int maxLength) {
+        // UTF-8 is at most 4 bytes per character; return early in the common case where
+        // the name can't possibly be over the limit given its string length.
+        if (originalName.length() <= maxLength / 4) return originalName;
+
+        final Charset utf8 = StandardCharsets.UTF_8;
+        final CharsetEncoder encoder = utf8.newEncoder();
+        final ByteBuffer out = ByteBuffer.allocate(maxLength);
+        // encode will write as many characters as possible to the out buffer, and just
+        // return an overflow code if there were too many characters (no need to check the
+        // return code here, this method truncates the name on purpose).
+        encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
+        return new String(out.array(), 0, out.position(), utf8);
+    }
+}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index eed9aeb..6776920 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityResources;
 import android.net.EthernetManager;
 import android.net.EthernetNetworkSpecifier;
 import android.net.IpConfiguration;
@@ -50,6 +49,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.InterfaceParams;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.FileDescriptor;
 import java.util.Objects;
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index d520757..1f22b02 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -25,7 +25,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.net.ConnectivityResources;
 import android.net.EthernetManager;
 import android.net.IEthernetServiceListener;
 import android.net.INetd;
@@ -57,6 +56,7 @@
 import com.android.net.module.util.netlink.NetlinkMessage;
 import com.android.net.module.util.netlink.RtNetlinkLinkMessage;
 import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.FileDescriptor;
 import java.net.InetAddress;
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 961337d..f977a27 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -83,7 +83,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityResources;
 import android.net.DataUsageRequest;
 import android.net.INetd;
 import android.net.INetworkStatsService;
@@ -175,6 +174,7 @@
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.server.BpfNetMaps;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -946,7 +946,11 @@
     @GuardedBy("mStatsLock")
     private void shutdownLocked() {
         final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
-        tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+        try {
+            tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+        } catch (IllegalStateException e) {
+            Log.i(TAG, "shutdownLocked: error when unregister tethering, ignored. e=" + e);
+        }
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mRemovedReceiver);
         mContext.unregisterReceiver(mUserReceiver);
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 77cffda..9ced44e 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -54,6 +54,10 @@
   if (!isOk(status)) {
     uid_t uid = getuid();
     ALOGE("BpfNetMaps jni init failure as uid=%d", uid);
+    // We probably only ever get called from system_server (ie. AID_SYSTEM)
+    // or from tests, and never from network_stack (ie. AID_NETWORK_STACK).
+    // However, if we ever do add calls from production network_stack code
+    // we do want to make sure this initializes correctly.
     // TODO: Fix tests to not use this jni lib, so we can unconditionally abort()
     if (uid == AID_SYSTEM || uid == AID_NETWORK_STACK) abort();
   }
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 059b716..d966070 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -90,11 +90,6 @@
 
 #undef ALOGF
 
-bool isGsiImage() {
-    // this implementation matches 2 other places in the codebase (same function name too)
-    return !access("/system/system_ext/etc/init/init.gsi.rc", F_OK);
-}
-
 static const char* kClatdDir = "/apex/com.android.tethering/bin/for-system";
 static const char* kClatdBin = "/apex/com.android.tethering/bin/for-system/clatd";
 
@@ -135,14 +130,6 @@
 
 #undef V2
 
-    // HACK: Some old vendor kernels lack ~5.10 backport of 'bpffs selinux genfscon' support.
-    // This is *NOT* supported, but let's allow, at least for now, U+ GSI to boot on them.
-    // (without this hack pixel5 R vendor + U gsi breaks)
-    if (isGsiImage() && !bpf::isAtLeastKernelVersion(5, 10, 0)) {
-        ALOGE("GSI with *BAD* pre-5.10 kernel lacking bpffs selinux genfscon support.");
-        return;
-    }
-
     if (fatal) abort();
 }
 
@@ -485,11 +472,15 @@
 static constexpr int WAITPID_ATTEMPTS = 50;
 static constexpr int WAITPID_RETRY_INTERVAL_US = 100000;
 
-static void stopClatdProcess(int pid) {
-    int err = kill(pid, SIGTERM);
-    if (err) {
-        err = errno;
+static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jclass clazz,
+                                                                      jint pid) {
+    if (pid <= 0) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
+        return;
     }
+
+    int err = kill(pid, SIGTERM);
+    if (err) err = errno;
     if (err == ESRCH) {
         ALOGE("clatd child process %d unexpectedly disappeared", pid);
         return;
@@ -518,23 +509,6 @@
     }
 }
 
-static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jclass clazz,
-                                                                      jstring iface, jstring pfx96,
-                                                                      jstring v4, jstring v6,
-                                                                      jint pid) {
-    ScopedUtfChars ifaceStr(env, iface);
-    ScopedUtfChars pfx96Str(env, pfx96);
-    ScopedUtfChars v4Str(env, v4);
-    ScopedUtfChars v6Str(env, v6);
-
-    if (pid <= 0) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
-        return;
-    }
-
-    stopClatdProcess(pid);
-}
-
 static jlong com_android_server_connectivity_ClatCoordinator_getSocketCookie(
         JNIEnv* env, jclass clazz, jobject sockJavaFd) {
     int sockFd = netjniutils::GetNativeFileDescriptor(env, sockJavaFd);
@@ -579,8 +553,7 @@
          "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/lang/"
          "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
          (void*)com_android_server_connectivity_ClatCoordinator_startClatd},
-        {"native_stopClatd",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
+        {"native_stopClatd", "(I)V",
          (void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
         {"native_getSocketCookie", "(Ljava/io/FileDescriptor;)J",
          (void*)com_android_server_connectivity_ClatCoordinator_getSocketCookie},
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 3d15d43..ed74430 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -22,8 +22,8 @@
 namespace android {
 
 int register_com_android_server_TestNetworkService(JNIEnv* env);
-int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
 int register_com_android_server_BpfNetMaps(JNIEnv* env);
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
 int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
 int register_android_server_net_NetworkStatsService(JNIEnv* env);
 
@@ -38,15 +38,15 @@
         return JNI_ERR;
     }
 
-    if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
-        return JNI_ERR;
-    }
-
-    if (register_com_android_server_BpfNetMaps(env) < 0) {
-        return JNI_ERR;
-    }
-
     if (android::modules::sdklevel::IsAtLeastT()) {
+        if (register_com_android_server_BpfNetMaps(env) < 0) {
+            return JNI_ERR;
+        }
+
+        if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
+            return JNI_ERR;
+        }
+
         if (register_android_server_net_NetworkStatsFactory(env) < 0) {
             return JNI_ERR;
         }
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index b4fce37..84e581e 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -281,8 +281,8 @@
         if (sEnableJavaBpfMap == null) {
             sEnableJavaBpfMap = SdkLevel.isAtLeastU() ||
                     DeviceConfigUtils.isFeatureEnabled(context,
-                    DeviceConfig.NAMESPACE_TETHERING, BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP,
-                    false /* defaultValue */);
+                            DeviceConfig.NAMESPACE_TETHERING, BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP,
+                            DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultValue */);
         }
         Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap);
 
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ba503e0..fa770a9 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -91,7 +91,7 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.VPN_UID;
-import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 import static android.system.OsConstants.ETH_P_ALL;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
@@ -134,7 +134,6 @@
 import android.net.ConnectivityManager.BlockedReason;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.RestrictBackgroundStatus;
-import android.net.ConnectivityResources;
 import android.net.ConnectivitySettingsManager;
 import android.net.DataStallReportParcelable;
 import android.net.DnsResolverServiceManager;
@@ -244,6 +243,7 @@
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -280,11 +280,13 @@
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
 import com.android.server.connectivity.ConnectivityFlags;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.DscpPolicyTracker;
 import com.android.server.connectivity.FullScore;
 import com.android.server.connectivity.InvalidTagException;
+import com.android.server.connectivity.KeepaliveResourceUtil;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -310,11 +312,13 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1389,9 +1393,9 @@
         /**
          * @see DeviceConfigUtils#isFeatureEnabled
          */
-        public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
-            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
-                    TETHERING_MODULE_NAME, defaultEnabled);
+        public boolean isFeatureEnabled(Context context, String name) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING, name,
+                    TETHERING_MODULE_NAME, false /* defaultValue */);
         }
 
         /**
@@ -1485,6 +1489,18 @@
                 @NonNull final UserHandle user) {
             return CompatChanges.isChangeEnabled(changeId, packageName, user);
         }
+
+        /**
+         * Call {@link InetDiagMessage#destroyLiveTcpSockets(Set, Set)}
+         *
+         * @param ranges target uid ranges
+         * @param exemptUids uids to skip close socket
+         */
+        public void destroyLiveTcpSockets(@NonNull final Set<Range<Integer>> ranges,
+                @NonNull final Set<Integer> exemptUids)
+                throws SocketException, InterruptedIOException, ErrnoException {
+            InetDiagMessage.destroyLiveTcpSockets(ranges, exemptUids);
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -7904,16 +7920,27 @@
         return captivePortalBuilder.build();
     }
 
-    private String makeNflogPrefix(String iface, long networkHandle) {
+    @VisibleForTesting
+    static String makeNflogPrefix(String iface, long networkHandle) {
         // This needs to be kept in sync and backwards compatible with the decoding logic in
         // NetdEventListenerService, which is non-mainline code.
         return SdkLevel.isAtLeastU() ? (networkHandle + ":" + iface) : ("iface:" + iface);
     }
 
+    private static boolean isWakeupMarkingSupported(NetworkCapabilities capabilities) {
+        if (capabilities.hasTransport(TRANSPORT_WIFI)) {
+            return true;
+        }
+        if (SdkLevel.isAtLeastU() && capabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            return true;
+        }
+        return false;
+    }
+
     private void wakeupModifyInterface(String iface, NetworkAgentInfo nai, boolean add) {
         // Marks are only available on WiFi interfaces. Checking for
         // marks on unsupported interfaces is harmless.
-        if (!nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+        if (!isWakeupMarkingSupported(nai.networkCapabilities)) {
             return;
         }
 
@@ -8448,11 +8475,11 @@
         return stableRanges;
     }
 
-    private void maybeCloseSockets(NetworkAgentInfo nai, UidRangeParcel[] ranges,
-            int[] exemptUids) {
+    private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges,
+            Set<Integer> exemptUids) {
         if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
             try {
-                mNetd.socketDestroy(ranges, exemptUids);
+                mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUids);
             } catch (Exception e) {
                 loge("Exception in socket destroy: ", e);
             }
@@ -8460,16 +8487,16 @@
     }
 
     private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
-        int[] exemptUids = new int[2];
+        final Set<Integer> exemptUids = new ArraySet<>();
         // TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
         // by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
         // starting a legacy VPN, and remove VPN_UID here. (b/176542831)
-        exemptUids[0] = VPN_UID;
-        exemptUids[1] = nai.networkCapabilities.getOwnerUid();
+        exemptUids.add(VPN_UID);
+        exemptUids.add(nai.networkCapabilities.getOwnerUid());
         UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
 
         // Close sockets before modifying uid ranges so that RST packets can reach to the server.
-        maybeCloseSockets(nai, ranges, exemptUids);
+        maybeCloseSockets(nai, uidRanges, exemptUids);
         try {
             if (add) {
                 mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -8483,7 +8510,7 @@
                     " on netId " + nai.network.netId + ". " + e);
         }
         // Close sockets that established connection while requesting netd.
-        maybeCloseSockets(nai, ranges, exemptUids);
+        maybeCloseSockets(nai, uidRanges, exemptUids);
     }
 
     private boolean isProxySetOnAnyDefaultNetwork() {
@@ -10021,6 +10048,16 @@
     }
 
     @Override
+    public int[] getSupportedKeepalives() {
+        enforceAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_SETTINGS,
+                // Backwards compatibility with CTS 13
+                android.Manifest.permission.QUERY_ALL_PACKAGES);
+
+        return BinderUtils.withCleanCallingIdentity(() ->
+                KeepaliveResourceUtil.getSupportedKeepalives(mContext));
+    }
+
+    @Override
     public void factoryReset() {
         enforceSettingsPermission();
 
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index ee8ab68..7c23816 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -842,7 +842,7 @@
          */
         public boolean isFeatureEnabled(@NonNull final String name, final boolean defaultEnabled) {
             return DeviceConfigUtils.isFeatureEnabled(mContext, NAMESPACE_TETHERING, name,
-                    defaultEnabled);
+                    DeviceConfigUtils.TETHERING_MODULE_NAME, defaultEnabled);
         }
 
         /**
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 5d04632..fbe706c 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -237,9 +237,8 @@
         /**
          * Stop clatd.
          */
-        public void stopClatd(String iface, String pfx96, String v4, String v6, int pid)
-                throws IOException {
-            native_stopClatd(iface, pfx96, v4, v6, pid);
+        public void stopClatd(int pid) throws IOException {
+            native_stopClatd(pid);
         }
 
         /**
@@ -843,9 +842,7 @@
         Log.i(TAG, "Stopping clatd pid=" + mClatdTracker.pid + " on " + mClatdTracker.iface);
 
         maybeStopBpf(mClatdTracker);
-        mDeps.stopClatd(mClatdTracker.iface, mClatdTracker.pfx96.getHostAddress(),
-                mClatdTracker.v4.getHostAddress(), mClatdTracker.v6.getHostAddress(),
-                mClatdTracker.pid);
+        mDeps.stopClatd(mClatdTracker.pid);
         untagSocket(mClatdTracker.cookie);
 
         Log.i(TAG, "clatd on " + mClatdTracker.iface + " stopped");
@@ -944,7 +941,6 @@
     private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6,
             FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)
             throws IOException;
-    private static native void native_stopClatd(String iface, String pfx96, String v4, String v6,
-            int pid) throws IOException;
+    private static native void native_stopClatd(int pid) throws IOException;
     private static native long native_getSocketCookie(FileDescriptor sock) throws IOException;
 }
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 122ea1c..9039a14 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -61,6 +61,6 @@
      */
     public void loadFlags(ConnectivityService.Dependencies deps, Context ctx) {
         mNoRematchAllRequestsOnRegister = deps.isFeatureEnabled(
-                ctx, NO_REMATCH_ALL_REQUESTS_ON_REGISTER, false /* defaultEnabled */);
+                ctx, NO_REMATCH_ALL_REQUESTS_ON_REGISTER);
     }
 }
diff --git a/framework/src/android/net/ConnectivityResources.java b/service/src/com/android/server/connectivity/ConnectivityResources.java
similarity index 67%
rename from framework/src/android/net/ConnectivityResources.java
rename to service/src/com/android/server/connectivity/ConnectivityResources.java
index 18f0de0..9089e4a 100644
--- a/framework/src/android/net/ConnectivityResources.java
+++ b/service/src/com/android/server/connectivity/ConnectivityResources.java
@@ -14,22 +14,16 @@
  * limitations under the License.
  */
 
-package android.net;
-
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+package com.android.server.connectivity;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.List;
+import com.android.net.module.util.DeviceConfigUtils;
 
 /**
  * Utility to obtain the {@link com.android.server.ConnectivityService} {@link Resources}, in the
@@ -37,10 +31,6 @@
  * @hide
  */
 public class ConnectivityResources {
-    private static final String RESOURCES_APK_INTENT =
-            "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK";
-    private static final String RES_PKG_DIR = "/apex/com.android.tethering/";
-
     @NonNull
     private final Context mContext;
 
@@ -76,21 +66,10 @@
             return mResourcesContext;
         }
 
-        final List<ResolveInfo> pkgs = mContext.getPackageManager()
-                .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY);
-        pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith(RES_PKG_DIR));
-        if (pkgs.size() > 1) {
-            Log.wtf(ConnectivityResources.class.getSimpleName(),
-                    "More than one package found: " + pkgs);
-        }
-        if (pkgs.isEmpty()) {
-            throw new IllegalStateException("No connectivity resource package found");
-        }
-
+        final String resPkg = DeviceConfigUtils.getConnectivityResourcesPackageName(mContext);
         final Context pkgContext;
         try {
-            pkgContext = mContext.createPackageContext(
-                    pkgs.get(0).activityInfo.applicationInfo.packageName, 0 /* flags */);
+            pkgContext = mContext.createPackageContext(resPkg, 0 /* flags */);
         } catch (PackageManager.NameNotFoundException e) {
             throw new IllegalStateException("Resolved package not found", e);
         }
diff --git a/service/src/com/android/server/connectivity/KeepaliveResourceUtil.java b/service/src/com/android/server/connectivity/KeepaliveResourceUtil.java
new file mode 100644
index 0000000..92b080d
--- /dev/null
+++ b/service/src/com/android/server/connectivity/KeepaliveResourceUtil.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkCapabilities;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+
+import com.android.connectivity.resources.R;
+
+/**
+ * Utilities to fetch keepalive configuration from resources.
+ */
+public abstract class KeepaliveResourceUtil {
+
+    /**
+     * Read supported keepalive count for each transport type from overlay resource.
+     *
+     * @param context The context to read resource from.
+     * @return An array of supported keepalive count for each transport type.
+     */
+    @NonNull
+    public static int[] getSupportedKeepalives(@NonNull Context context) {
+        String[] res = null;
+        try {
+            final ConnectivityResources connRes = new ConnectivityResources(context);
+            res = connRes.get().getStringArray(R.array.config_networkSupportedKeepaliveCount);
+        } catch (Resources.NotFoundException unused) {
+        }
+        if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
+
+        final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
+        for (final String row : res) {
+            if (TextUtils.isEmpty(row)) {
+                throw new KeepaliveDeviceConfigurationException("Empty string");
+            }
+            final String[] arr = row.split(",");
+            if (arr.length != 2) {
+                throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
+            }
+
+            int transport;
+            int supported;
+            try {
+                transport = Integer.parseInt(arr[0]);
+                supported = Integer.parseInt(arr[1]);
+            } catch (NumberFormatException e) {
+                throw new KeepaliveDeviceConfigurationException("Invalid number format");
+            }
+
+            if (!NetworkCapabilities.isValidTransport(transport)) {
+                throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
+            }
+
+            if (supported < 0) {
+                throw new KeepaliveDeviceConfigurationException(
+                        "Invalid supported count " + supported + " for "
+                                + NetworkCapabilities.transportNameOf(transport));
+            }
+            ret[transport] = supported;
+        }
+        return ret;
+    }
+
+    /**
+     * An exception thrown when the keepalive resource configuration is invalid.
+     */
+    public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
+        public KeepaliveDeviceConfigurationException(final String msg) {
+            super(msg);
+        }
+    }
+}
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 60485b3..8c170bc 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -37,7 +37,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.net.ConnectivityResources;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
 import android.net.InvalidPacketException;
@@ -111,7 +110,7 @@
         mTcpController = new TcpKeepaliveController(handler);
         mContext = context;
 
-        mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
+        mSupportedKeepalives = KeepaliveResourceUtil.getSupportedKeepalives(context);
 
         final ConnectivityResources res = new ConnectivityResources(mContext);
         mReservedPrivilegedSlots = res.get().getInteger(
diff --git a/service/src/com/android/server/connectivity/LingerMonitor.java b/service/src/com/android/server/connectivity/LingerMonitor.java
index df34ce7..8503fcc 100644
--- a/service/src/com/android/server/connectivity/LingerMonitor.java
+++ b/service/src/com/android/server/connectivity/LingerMonitor.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.net.NetworkCapabilities;
 import android.os.SystemClock;
 import android.os.UserHandle;
diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
index 58196f7..93018bb 100644
--- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -28,7 +28,6 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.net.ConnectivityResources;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index 509110d..15d0925 100644
--- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
@@ -18,6 +18,11 @@
 
 import static android.system.OsConstants.*;
 
+import static com.android.net.module.util.NetworkStackConstants.ICMP_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.InetAddresses;
@@ -33,6 +38,7 @@
 import android.system.Os;
 import android.system.StructTimeval;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -172,7 +178,7 @@
         }
     }
 
-    private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
+    private final Map<Pair<InetAddress, Integer>, Measurement> mIcmpChecks = new HashMap<>();
     private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks =
             new HashMap<>();
     private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
@@ -205,17 +211,21 @@
             mLinkProperties.addDnsServer(TEST_DNS6);
         }
 
+        final int mtu = mLinkProperties.getMtu();
         for (RouteInfo route : mLinkProperties.getRoutes()) {
             if (route.getType() == RouteInfo.RTN_UNICAST && route.hasGateway()) {
                 InetAddress gateway = route.getGateway();
-                prepareIcmpMeasurement(gateway);
+                // Use mtu in the route if exists. Otherwise, use the one in the link property.
+                final int routeMtu = route.getMtu();
+                prepareIcmpMeasurements(gateway, (routeMtu > 0) ? routeMtu : mtu);
                 if (route.isIPv6Default()) {
                     prepareExplicitSourceIcmpMeasurements(gateway);
                 }
             }
         }
+
         for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
-            prepareIcmpMeasurement(nameserver);
+            prepareIcmpMeasurements(nameserver, mtu);
             prepareDnsMeasurement(nameserver);
 
             // Unlike the DnsResolver which doesn't do certificate validation in opportunistic mode,
@@ -261,11 +271,50 @@
                 localAddr.getHostAddress(), inetSockAddr.getPort());
     }
 
-    private void prepareIcmpMeasurement(InetAddress target) {
-        if (!mIcmpChecks.containsKey(target)) {
-            Measurement measurement = new Measurement();
-            measurement.thread = new Thread(new IcmpCheck(target, measurement));
-            mIcmpChecks.put(target, measurement);
+    private static int getHeaderLen(@NonNull InetAddress target) {
+        // Convert IPv4 mapped v6 address to v4 if any.
+        try {
+            final InetAddress addr = InetAddress.getByAddress(target.getAddress());
+            // An ICMPv6 header is technically 4 bytes, but the implementation in IcmpCheck#run()
+            // will always fill in another 4 bytes padding in the v6 diagnostic packets, so the size
+            // before icmp data is always 8 bytes in the implementation of ICMP diagnostics for both
+            // v4 and v6 packets. Thus, it's fine to use the v4 header size in the length
+            // calculation.
+            if (addr instanceof Inet6Address) {
+                return IPV6_HEADER_LEN + ICMP_HEADER_LEN;
+            }
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Create InetAddress fail(" + target + "): " + e);
+        }
+
+        return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN;
+    }
+
+    private void prepareIcmpMeasurements(@NonNull InetAddress target, int targetNetworkMtu) {
+        // Test with different size payload ICMP.
+        // 1. Test with 0 payload.
+        addPayloadIcmpMeasurement(target, 0);
+        final int header = getHeaderLen(target);
+        // 2. Test with full size MTU.
+        addPayloadIcmpMeasurement(target, targetNetworkMtu - header);
+        // 3. If v6, make another measurement with the full v6 min MTU, unless that's what
+        //    was done above.
+        if ((target instanceof Inet6Address) && (targetNetworkMtu != IPV6_MIN_MTU)) {
+            addPayloadIcmpMeasurement(target, IPV6_MIN_MTU - header);
+        }
+    }
+
+    private void addPayloadIcmpMeasurement(@NonNull InetAddress target, int payloadLen) {
+        // This can happen if the there is no mtu filled(which is 0) in the link property.
+        // The value becomes negative after minus header length.
+        if (payloadLen < 0) return;
+
+        final Pair<InetAddress, Integer> lenTarget =
+                new Pair<>(target, Integer.valueOf(payloadLen));
+        if (!mIcmpChecks.containsKey(lenTarget)) {
+            final Measurement measurement = new Measurement();
+            measurement.thread = new Thread(new IcmpCheck(target, payloadLen, measurement));
+            mIcmpChecks.put(lenTarget, measurement);
         }
     }
 
@@ -276,7 +325,7 @@
                 Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target);
                 if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) {
                     Measurement measurement = new Measurement();
-                    measurement.thread = new Thread(new IcmpCheck(source, target, measurement));
+                    measurement.thread = new Thread(new IcmpCheck(source, target, 0, measurement));
                     mExplicitSourceIcmpChecks.put(srcTarget, measurement);
                 }
             }
@@ -334,8 +383,8 @@
         ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount());
 
         // Sort measurements IPv4 first.
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet4Address) {
+        for (Map.Entry<Pair<InetAddress, Integer>, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet4Address) {
                 measurements.add(entry.getValue());
             }
         }
@@ -357,8 +406,8 @@
         }
 
         // IPv6 measurements second.
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet6Address) {
+        for (Map.Entry<Pair<InetAddress, Integer>, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet6Address) {
                 measurements.add(entry.getValue());
             }
         }
@@ -489,8 +538,11 @@
         private static final int PACKET_BUFSIZE = 512;
         private final int mProtocol;
         private final int mIcmpType;
+        private final int mPayloadSize;
+        // The length parameter is effectively the -s flag to ping/ping6 to specify the number of
+        // data bytes to be sent.
+        IcmpCheck(InetAddress source, InetAddress target, int length, Measurement measurement) {
 
-        public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) {
             super(source, target, measurement);
 
             if (mAddressFamily == AF_INET6) {
@@ -502,12 +554,13 @@
                 mIcmpType = NetworkConstants.ICMPV4_ECHO_REQUEST_TYPE;
                 mMeasurement.description = "ICMPv4";
             }
-
-            mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
+            mPayloadSize = length;
+            mMeasurement.description += " payloadLength{" + mPayloadSize  + "}"
+                    + " dst{" + mTarget.getHostAddress() + "}";
         }
 
-        public IcmpCheck(InetAddress target, Measurement measurement) {
-            this(null, target, measurement);
+        IcmpCheck(InetAddress target, int length, Measurement measurement) {
+            this(null, target, length, measurement);
         }
 
         @Override
@@ -523,9 +576,11 @@
             mMeasurement.description += " src{" + socketAddressToString(mSocketAddress) + "}";
 
             // Build a trivial ICMP packet.
-            final byte[] icmpPacket = {
-                    (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0  // ICMP header
-            };
+            // The v4 ICMP header ICMP_HEADER_LEN (which is 8) and v6 is only 4 bytes (4 bytes
+            // message body followed by header before the payload).
+            // Use 8 bytes for both v4 and v6 for simplicity.
+            final byte[] icmpPacket = new byte[ICMP_HEADER_LEN + mPayloadSize];
+            icmpPacket[0] = (byte) mIcmpType;
 
             int count = 0;
             mMeasurement.startTime = now();
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index cdc0aa9..8b0cb7c 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -30,7 +30,6 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
-import android.net.ConnectivityResources;
 import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiInfo;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index c28ee64..624acd3 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -154,7 +154,6 @@
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
@@ -809,26 +808,12 @@
                 mOldPrivateDnsSpecifier);
     }
 
-    // TODO: replace with CtsNetUtils.awaitPrivateDnsSetting in Q or above.
     private void expectPrivateDnsHostname(final String hostname) throws Exception {
-        final NetworkRequest request = new NetworkRequest.Builder()
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .build();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final NetworkCallback callback = new NetworkCallback() {
-            @Override
-            public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
-                if (network.equals(mNetwork) &&
-                        Objects.equals(lp.getPrivateDnsServerName(), hostname)) {
-                    latch.countDown();
-                }
-            }
-        };
-
-        registerNetworkCallback(request, callback);
-
-        assertTrue("Private DNS hostname was not " + hostname + " after " + TIMEOUT_MS + "ms",
-                latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
+            // Wait for private DNS setting to propagate.
+            mCtsNetUtils.awaitPrivateDnsSetting("Test wait private DNS setting timeout",
+                    network, hostname, false);
+        }
     }
 
     private void setAndVerifyPrivateDns(boolean strictMode) throws Exception {
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 999614c..68e36ff 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -37,9 +37,6 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
-    <!-- TODO (b/186093901): remove after fixing resource querying -->
-    <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) :
              android.permission.MANAGE_TEST_NETWORKS
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index a147147..8b059e3 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -1655,7 +1655,8 @@
         final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
 
         // Get number of supported concurrent keepalives for testing network.
-        final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(mContext);
+        final int[] keepalivesPerTransport = runAsShell(NETWORK_SETTINGS,
+                () -> mCm.getSupportedKeepalives());
         return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
                 keepalivesPerTransport, nc);
     }
@@ -1670,6 +1671,9 @@
      * keepalives is set to 0.
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @Test
     public void testKeepaliveWifiUnsupported() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -1686,6 +1690,9 @@
     }
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @Test
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testCreateTcpKeepalive() throws Exception {
@@ -1894,6 +1901,9 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testSocketKeepaliveLimitWifi() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -1944,6 +1954,9 @@
      */
     @AppModeFull(reason = "Cannot request network in instant app mode")
     @Test
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testSocketKeepaliveLimitTelephony() throws Exception {
         if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
@@ -1990,6 +2003,9 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testSocketKeepaliveUnprivileged() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -2112,7 +2128,12 @@
     @AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
     @Test
     public void testSetAirplaneMode() throws Exception{
-        final boolean supportWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI);
+        // Starting from T, wifi supports airplane mode enhancement which may not disconnect wifi
+        // when airplane mode is on. The actual behavior that the device will have could only be
+        // checked with hidden wifi APIs(see Settings.Secure.WIFI_APM_STATE). Thus, stop verifying
+        // wifi on T+ device.
+        final boolean verifyWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && !SdkLevel.isAtLeastT();
         final boolean supportTelephony = mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
         // store the current state of airplane mode
         final boolean isAirplaneModeEnabled = isAirplaneModeEnabled();
@@ -2123,7 +2144,7 @@
         // Verify that networks are available as expected if wifi or cell is supported. Continue the
         // test if none of them are supported since test should still able to verify the permission
         // mechanism.
-        if (supportWifi) {
+        if (verifyWifi) {
             mCtsNetUtils.ensureWifiConnected();
             registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
         }
@@ -2147,7 +2168,7 @@
             // Verify that the enabling airplane mode takes effect as expected to prevent flakiness
             // caused by fast airplane mode switches. Ensure network lost before turning off
             // airplane mode.
-            if (supportWifi) waitForLost(wifiCb);
+            if (verifyWifi) waitForLost(wifiCb);
             if (supportTelephony) waitForLost(telephonyCb);
 
             // Verify we can disable Airplane Mode with correct permission:
@@ -2156,7 +2177,7 @@
             // Verify that turning airplane mode off takes effect as expected.
             // connectToCell only registers a request, it cannot / does not need to be called twice
             mCtsNetUtils.ensureWifiConnected();
-            if (supportWifi) waitForAvailable(wifiCb);
+            if (verifyWifi) waitForAvailable(wifiCb);
             if (supportTelephony) waitForAvailable(telephonyCb);
         } finally {
             // Restore the previous state of airplane mode and permissions:
@@ -2957,13 +2978,13 @@
 
         allowBadWifi();
 
-        final Network cellNetwork = mCtsNetUtils.connectToCell();
-        final Network wifiNetwork = prepareValidatedNetwork();
-
-        registerDefaultNetworkCallback(defaultCb);
-        registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
-
         try {
+            final Network cellNetwork = mCtsNetUtils.connectToCell();
+            final Network wifiNetwork = prepareValidatedNetwork();
+
+            registerDefaultNetworkCallback(defaultCb);
+            registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+
             // Verify wifi is the default network.
             defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
                     entry -> wifiNetwork.equals(entry.getNetwork()));
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 691ab99..17a9ca2 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -18,21 +18,18 @@
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
-import android.content.Context;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkUtils;
 import android.net.cts.util.CtsNetUtils;
 import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.test.AndroidTestCase;
 
-import java.util.ArrayList;
-
 public class MultinetworkApiTest extends AndroidTestCase {
 
     static {
@@ -75,26 +72,8 @@
         super.tearDown();
     }
 
-    private Network[] getTestableNetworks() {
-        final ArrayList<Network> testableNetworks = new ArrayList<Network>();
-        for (Network network : mCM.getAllNetworks()) {
-            final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
-            if (nc != null
-                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                testableNetworks.add(network);
-            }
-        }
-
-        assertTrue(
-                "This test requires that at least one network be connected. " +
-                "Please ensure that the device is connected to a network.",
-                testableNetworks.size() >= 1);
-        return testableNetworks.toArray(new Network[0]);
-    }
-
     public void testGetaddrinfo() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runGetaddrinfoCheck(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -109,7 +88,7 @@
         assertNull(mCM.getProcessDefaultNetwork());
         assertEquals(0, NetworkUtils.getBoundNetworkForProcess());
 
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             mCM.setProcessDefaultNetwork(null);
             assertNull(mCM.getProcessDefaultNetwork());
 
@@ -128,7 +107,7 @@
             mCM.setProcessDefaultNetwork(null);
         }
 
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             NetworkUtils.bindProcessToNetwork(0);
             assertNull(mCM.getBoundNetworkForProcess());
 
@@ -148,7 +127,7 @@
 
     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testSetsocknetwork() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runSetsocknetwork(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -158,7 +137,7 @@
     }
 
     public void testNativeDatagramTransmission() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runDatagramCheck(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -181,7 +160,7 @@
 
     public void testNetworkHandle() {
         // Test Network -> NetworkHandle -> Network results in the same Network.
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             long networkHandle = network.getNetworkHandle();
             Network newNetwork = Network.fromNetworkHandle(networkHandle);
             assertEquals(newNetwork, network);
@@ -203,7 +182,7 @@
     }
 
     public void testResNApi() throws Exception {
-        final Network[] testNetworks = getTestableNetworks();
+        final Network[] testNetworks = mCtsNetUtils.getTestableNetworks();
 
         for (Network network : testNetworks) {
             // Throws AssertionError directly in jni function if test fail.
@@ -229,7 +208,7 @@
         // b/144521720
         try {
             mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
-            for (Network network : getTestableNetworks()) {
+            for (Network network : mCtsNetUtils.getTestableNetworks()) {
               // Wait for private DNS setting to propagate.
               mCtsNetUtils.awaitPrivateDnsSetting("NxDomain test wait private DNS setting timeout",
                         network, GOOGLE_PRIVATE_DNS_SERVER, true);
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
index fcfecad..2704dd3 100644
--- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -30,6 +30,7 @@
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
+import android.util.Log
 import androidx.test.InstrumentationRegistry
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -41,6 +42,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.Collections
 
 // 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
@@ -64,10 +66,11 @@
 @IgnoreUpTo(Build.VERSION_CODES.R)
 @RunWith(DevSdkIgnoreRunner::class)
 class NetworkScoreTest {
+    private val TAG = javaClass.simpleName
     private val mCm = testContext.getSystemService(ConnectivityManager::class.java)
-    private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
+    private val mHandlerThread = HandlerThread("$TAG handler thread")
     private val mHandler by lazy { Handler(mHandlerThread.looper) }
-    private val agentsToCleanUp = mutableListOf<NetworkAgent>()
+    private val agentsToCleanUp = Collections.synchronizedList(mutableListOf<NetworkAgent>())
     private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
 
     @Before
@@ -83,15 +86,18 @@
                     .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), cb, mHandler
             )
         }
+        Log.i(TAG, "Teardown on thread ${System.identityHashCode(Thread.currentThread())} " +
+                "cleaning up ${agentsToCleanUp.size} agents")
         agentsToCleanUp.forEach {
+            Log.i(TAG, "Unregister agent for net ${it.network}")
             it.unregister()
             agentCleanUpCb.eventuallyExpect<CallbackEntry.Lost> { cb -> cb.network == it.network }
         }
         mCm.unregisterNetworkCallback(agentCleanUpCb)
 
+        callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
         mHandlerThread.quitSafely()
         mHandlerThread.join()
-        callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
     }
 
     // Returns a networkCallback that sends onAvailable on the best network with TRANSPORT_TEST.
@@ -145,6 +151,8 @@
         val agent = object : NetworkAgent(context, looper, "NetworkScore test agent", nc,
                 LinkProperties(), score, config, NetworkProvider(context, looper,
                 "NetworkScore test provider")) {}.also {
+            Log.i(TAG, "Add on thread ${System.identityHashCode(Thread.currentThread())} " +
+                    "agent to clean up $it")
             agentsToCleanUp.add(it)
         }
         runWithShellPermissionIdentity({ agent.register() }, MANAGE_TEST_NETWORKS)
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 d817630..0c4f794 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
@@ -57,6 +57,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
@@ -68,6 +70,8 @@
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -506,17 +510,18 @@
      * @throws InterruptedException If the thread is interrupted.
      */
     public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
-            @NonNull String server, boolean requiresValidatedServer) throws InterruptedException {
+            @Nullable String server, boolean requiresValidatedServer) throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
-        NetworkCallback callback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             @Override
             public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
                 Log.i(TAG, "Link properties of network " + n + " changed to " + lp);
                 if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
                     return;
                 }
-                if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
+                Log.i(TAG, "Set private DNS server to " + server);
+                if (network.equals(n) && Objects.equals(server, lp.getPrivateDnsServerName())) {
                     latch.countDown();
                 }
             }
@@ -539,6 +544,27 @@
     }
 
     /**
+     * Get all testable Networks with internet capability.
+     */
+    public Network[] getTestableNetworks() {
+        final ArrayList<Network> testableNetworks = new ArrayList<Network>();
+        for (Network network : mCm.getAllNetworks()) {
+            final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+            if (nc != null
+                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                testableNetworks.add(network);
+            }
+        }
+
+        assertTrue("This test requires that at least one public Internet-providing"
+                        + " network be connected. Please ensure that the device is connected to"
+                        + " a network.",
+                testableNetworks.size() >= 1);
+        return testableNetworks.toArray(new Network[0]);
+    }
+
+    /**
      * Receiver that captures the last connectivity change's network type and state. Recognizes
      * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
      */
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 3c1340d..67e1296 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -25,7 +25,6 @@
 import android.content.ServiceConnection
 import android.content.res.Resources
 import android.net.ConnectivityManager
-import android.net.ConnectivityResources
 import android.net.IDnsResolver
 import android.net.INetd
 import android.net.LinkProperties
@@ -50,11 +49,15 @@
 import com.android.server.ConnectivityService
 import com.android.server.NetworkAgentWrapper
 import com.android.server.TestNetIdManager
+import com.android.server.connectivity.ConnectivityResources
 import com.android.server.connectivity.MockableSystemProperties
 import com.android.server.connectivity.MultinetworkPolicyTracker
 import com.android.server.connectivity.ProxyTracker
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.TestableNetworkCallback
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
 import org.junit.After
 import org.junit.Before
 import org.junit.BeforeClass
@@ -73,9 +76,6 @@
 import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
 import org.mockito.Spy
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import kotlin.test.fail
 
 const val SERVICE_BIND_TIMEOUT_MS = 5_000L
 const val TEST_TIMEOUT_MS = 10_000L
@@ -215,8 +215,8 @@
                     inv.getArgument(2),
                     object : MultinetworkPolicyTracker.Dependencies() {
                         override fun getResourcesForActiveSubId(
-                            connResources: ConnectivityResources,
-                            activeSubId: Int
+                                connResources: ConnectivityResources,
+                                activeSubId: Int
                         ) = resources
                     })
         }.`when`(deps).makeMultinetworkPolicyTracker(any(), any(), any())
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index cca0b66..cb3a315 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.res.Resources
-import android.net.ConnectivityResources
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.MAX_TRANSPORT
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
@@ -27,7 +26,9 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.os.Build
 import androidx.test.filters.SmallTest
-import com.android.internal.R
+import com.android.connectivity.resources.R
+import com.android.server.connectivity.ConnectivityResources
+import com.android.server.connectivity.KeepaliveResourceUtil
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.After
@@ -37,7 +38,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.any
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 
@@ -53,14 +53,9 @@
 class KeepaliveUtilsTest {
 
     // Prepare mocked context with given resource strings.
-    private fun getMockedContextWithStringArrayRes(
-        id: Int,
-        name: String,
-        res: Array<out String?>?
-    ): Context {
+    private fun getMockedContextWithStringArrayRes(id: Int, res: Array<out String?>?): Context {
         val mockRes = mock(Resources::class.java)
         doReturn(res).`when`(mockRes).getStringArray(eq(id))
-        doReturn(id).`when`(mockRes).getIdentifier(eq(name), any(), any())
 
         return mock(Context::class.java).apply {
             doReturn(mockRes).`when`(this).getResources()
@@ -79,10 +74,10 @@
             try {
                 val mockContext = getMockedContextWithStringArrayRes(
                         R.array.config_networkSupportedKeepaliveCount,
-                        "config_networkSupportedKeepaliveCount", res)
-                KeepaliveUtils.getSupportedKeepalives(mockContext)
+                        res)
+                KeepaliveResourceUtil.getSupportedKeepalives(mockContext)
                 fail("Expected KeepaliveDeviceConfigurationException")
-            } catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) {
+            } catch (expected: KeepaliveResourceUtil.KeepaliveDeviceConfigurationException) {
             }
         }
 
@@ -108,8 +103,8 @@
 
         val mockContext = getMockedContextWithStringArrayRes(
                 R.array.config_networkSupportedKeepaliveCount,
-                "config_networkSupportedKeepaliveCount", validRes)
-        val actual = KeepaliveUtils.getSupportedKeepalives(mockContext)
+                validRes)
+        val actual = KeepaliveResourceUtil.getSupportedKeepalives(mockContext)
         assertArrayEquals(expectedValidRes, actual)
     }
 
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 1cc0c89..d186920 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -154,6 +154,7 @@
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
 import static com.android.server.ConnectivityService.createDeliveryGroupKeyForConnectivityAction;
+import static com.android.server.ConnectivityService.makeNflogPrefix;
 import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
 import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
 import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
@@ -255,7 +256,6 @@
 import android.net.ConnectivityManager.PacketKeepalive;
 import android.net.ConnectivityManager.PacketKeepaliveCallback;
 import android.net.ConnectivityManager.TooManyRequestsException;
-import android.net.ConnectivityResources;
 import android.net.ConnectivitySettingsManager;
 import android.net.ConnectivityThread;
 import android.net.DataStallReportParcelable;
@@ -388,6 +388,7 @@
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
 import com.android.server.connectivity.ConnectivityFlags;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.connectivity.MultinetworkPolicyTracker;
 import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
 import com.android.server.connectivity.Nat464Xlat;
@@ -502,7 +503,7 @@
     // complete before callbacks are verified.
     private static final int TEST_REQUEST_TIMEOUT_MS = 150;
 
-    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000;
+    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 2_000;
 
     private static final long TIMESTAMP = 1234L;
 
@@ -533,6 +534,10 @@
     private static final int TEST_PACKAGE_UID = 123;
     private static final int TEST_PACKAGE_UID2 = 321;
     private static final int TEST_PACKAGE_UID3 = 456;
+
+    private static final int PACKET_WAKEUP_MASK = 0xffff0000;
+    private static final int PACKET_WAKEUP_MARK = 0x88880000;
+
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
@@ -1853,7 +1858,7 @@
         final Context mockResContext = mock(Context.class);
         doReturn(mResources).when(mockResContext).getResources();
         ConnectivityResources.setResourcesContextForTest(mockResContext);
-        mDeps = new ConnectivityServiceDependencies(mockResContext);
+        mDeps = spy(new ConnectivityServiceDependencies(mockResContext));
         mAutoOnOffKeepaliveDependencies =
                 new AutomaticOnOffKeepaliveTrackerDependencies(mServiceContext);
         mService = new ConnectivityService(mServiceContext,
@@ -1910,9 +1915,14 @@
         doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
         doReturn(true).when(mResources)
                 .getBoolean(R.bool.config_cellular_radio_timesharing_capable);
+        doReturn(PACKET_WAKEUP_MASK).when(mResources).getInteger(
+                R.integer.config_networkWakeupPacketMask);
+        doReturn(PACKET_WAKEUP_MARK).when(mResources).getInteger(
+                R.integer.config_networkWakeupPacketMark);
     }
 
-    class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
+    // ConnectivityServiceDependencies is public to use Mockito.spy
+    public class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
         final ConnectivityResources mConnRes;
 
         ConnectivityServiceDependencies(final Context mockResContext) {
@@ -2067,12 +2077,12 @@
         }
 
         @Override
-        public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
+        public boolean isFeatureEnabled(Context context, String name) {
             switch (name) {
                 case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
                     return true;
                 default:
-                    return super.isFeatureEnabled(context, name, defaultEnabled);
+                    return super.isFeatureEnabled(context, name);
             }
         }
 
@@ -2148,6 +2158,12 @@
                 }
             }
         }
+
+        @Override
+        public void destroyLiveTcpSockets(final Set<Range<Integer>> ranges,
+                final Set<Integer> exemptUids) {
+            // This function is empty since the invocation of this method is verified by mocks
+        }
     }
 
     private class AutomaticOnOffKeepaliveTrackerDependencies
@@ -3369,8 +3385,10 @@
         // This test would be flaky with the default 120ms timer: that is short enough that
         // lingered networks are torn down before assertions can be run. We don't want to mock the
         // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful
-        // in detecting races.
-        mService.mLingerDelayMs = 300;
+        // in detecting races. Furthermore, sometimes the test is running while Phenotype is running
+        // so hot that the test doesn't get the CPU for multiple hundreds of milliseconds, so this
+        // needs to be suitably long.
+        mService.mLingerDelayMs = 2_000;
 
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED)
@@ -10384,6 +10402,16 @@
         return event;
     }
 
+    private void verifyWakeupModifyInterface(String iface, boolean add) throws RemoteException {
+        if (add) {
+            verify(mMockNetd).wakeupAddInterface(eq(iface), anyString(), anyInt(),
+                    anyInt());
+        } else {
+            verify(mMockNetd).wakeupDelInterface(eq(iface), anyString(), anyInt(),
+                    anyInt());
+        }
+    }
+
     private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
         if (inOrder != null) {
             return inOrder.verify(t);
@@ -10610,6 +10638,11 @@
         clat.interfaceRemoved(CLAT_MOBILE_IFNAME);
         networkCallback.assertNoCallback();
         verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
+
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
+        }
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mClatCoordinator);
         verifyNoMoreInteractions(mMockDnsResolver);
@@ -10646,6 +10679,10 @@
         assertRoutesAdded(cellNetId, stackedDefault);
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
 
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, true);
+        }
+
         // NAT64 prefix is removed. Expect that clat is stopped.
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
                 cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96));
@@ -10660,6 +10697,11 @@
                 cb -> cb.getLp().getStackedLinks().size() == 0);
         verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
         verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_MOBILE_IFNAME);
+
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
+        }
+
         // Clean up.
         mCellAgent.disconnect();
         networkCallback.expect(LOST, mCellAgent);
@@ -10672,6 +10714,11 @@
         } else {
             verify(mMockNetd, never()).setNetworkAllowlist(any());
         }
+
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(MOBILE_IFNAME, false);
+        }
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mClatCoordinator);
         reset(mMockNetd);
@@ -10701,6 +10748,11 @@
         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);
+
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(MOBILE_IFNAME, true);
+        }
+
         reset(mMockNetd);
         reset(mClatCoordinator);
 
@@ -10709,6 +10761,11 @@
         networkCallback.expect(LOST, mCellAgent);
         networkCallback.assertNoCallback();
         verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
+
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
+        }
+
         verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_CELLULAR)));
         verify(mMockNetd).networkDestroy(cellNetId);
@@ -10717,6 +10774,11 @@
         } else {
             verify(mMockNetd, never()).setNetworkAllowlist(any());
         }
+
+        if (SdkLevel.isAtLeastU()) {
+            verifyWakeupModifyInterface(MOBILE_IFNAME, false);
+        }
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mClatCoordinator);
 
@@ -12469,12 +12531,11 @@
 
     private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid)
             throws Exception {
-        InOrder inOrder = inOrder(mMockNetd);
-        ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class);
+        InOrder inOrder = inOrder(mMockNetd, mDeps);
+        final Set<Integer> exemptUidSet = new ArraySet<>(List.of(exemptUid, Process.VPN_UID));
 
-        inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
-                exemptUidCaptor.capture());
-        assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+        inOrder.verify(mDeps).destroyLiveTcpSockets(UidRange.toIntRanges(vpnRanges),
+                exemptUidSet);
 
         if (add) {
             inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(
@@ -12486,9 +12547,8 @@
                             toUidRangeStableParcels(vpnRanges), PREFERENCE_ORDER_VPN));
         }
 
-        inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
-                exemptUidCaptor.capture());
-        assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+        inOrder.verify(mDeps).destroyLiveTcpSockets(UidRange.toIntRanges(vpnRanges),
+                exemptUidSet);
     }
 
     @Test
@@ -17570,18 +17630,77 @@
         });
     }
 
+    private void verifyMtuSetOnWifiInterface(int mtu) throws Exception {
+        verify(mMockNetd, times(1)).interfaceSetMtu(WIFI_IFNAME, mtu);
+    }
+
+    private void verifyMtuNeverSetOnWifiInterface() throws Exception {
+        verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+    }
+
     @Test
-    public void testSendLinkPropertiesSetInterfaceMtu() throws Exception {
-        final int mtu = 1327;
+    public void testSendLinkPropertiesSetInterfaceMtuBeforeConnect() throws Exception {
+        final int mtu = 1281;
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiAgent.sendLinkProperties(lp);
-
         waitForIdle();
-        verify(mMockNetd).interfaceSetMtu(eq(WIFI_IFNAME), eq(mtu));
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
+
+        mWiFiAgent.connect(false /* validated */);
+        // The MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterface(mtu);
+    }
+
+    @Test
+    public void testSendLinkPropertiesUpdateInterfaceMtuBeforeConnect() throws Exception {
+        final int mtu = 1327;
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(WIFI_IFNAME);
+        lp.setMtu(mtu);
+
+        // Registering an agent with an MTU doesn't set the MTU...
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        waitForIdle();
+        verifyMtuNeverSetOnWifiInterface();
+        reset(mMockNetd);
+
+        // ... but prevents future updates with the same MTU from setting the MTU.
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        verifyMtuNeverSetOnWifiInterface();
+
+        // Updating with a different MTU does work.
+        lp.setMtu(mtu + 1);
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        verifyMtuSetOnWifiInterface(mtu + 1);
+        reset(mMockNetd);
+
+        mWiFiAgent.connect(false /* validated */);
+        // The MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterface(mtu + 1);
+    }
+
+    @Test
+    public void testSendLinkPropertiesUpdateInterfaceMtuAfterConnect() throws Exception {
+        final int mtu = 1327;
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(WIFI_IFNAME);
+        lp.setMtu(mtu);
+
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuNeverSetOnWifiInterface();
+
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        // The MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterface(mtu);
     }
 
     @Test
@@ -17592,14 +17711,15 @@
         lp.setMtu(mtu);
 
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
         LinkProperties lp2 = new LinkProperties(lp);
         lp2.setMtu(mtu2);
-
         mWiFiAgent.sendLinkProperties(lp2);
-
         waitForIdle();
-        verify(mMockNetd).interfaceSetMtu(eq(WIFI_IFNAME), eq(mtu2));
+        verifyMtuSetOnWifiInterface(mtu2);
     }
 
     @Test
@@ -17610,10 +17730,13 @@
         lp.setMtu(mtu);
 
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
-        mWiFiAgent.sendLinkProperties(new LinkProperties(lp));
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
+        mWiFiAgent.sendLinkProperties(new LinkProperties(lp));
         waitForIdle();
-        verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+        verifyMtuNeverSetOnWifiInterface();
     }
 
     @Test
@@ -17624,15 +17747,15 @@
         lp.setMtu(mtu);
 
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
-        LinkProperties lp2 = new LinkProperties();
-        assertNull(lp2.getInterfaceName());
-        lp2.setMtu(mtu);
-
+        LinkProperties lp2 = new LinkProperties(lp);
+        lp2.setInterfaceName(null);
         mWiFiAgent.sendLinkProperties(new LinkProperties(lp2));
-
         waitForIdle();
-        verify(mMockNetd, never()).interfaceSetMtu(any(), anyInt());
+        verifyMtuNeverSetOnWifiInterface();
     }
 
     @Test
@@ -17643,16 +17766,18 @@
         lp.setMtu(mtu);
 
         mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
         final String ifaceName2 = WIFI_IFNAME + "_2";
-        LinkProperties lp2 = new LinkProperties();
+        LinkProperties lp2 = new LinkProperties(lp);
         lp2.setInterfaceName(ifaceName2);
-        lp2.setMtu(mtu);
 
         mWiFiAgent.sendLinkProperties(new LinkProperties(lp2));
-
         waitForIdle();
-        verify(mMockNetd).interfaceSetMtu(eq(ifaceName2), eq(mtu));
+        verify(mMockNetd, times(1)).interfaceSetMtu(eq(ifaceName2), eq(mtu));
+        verifyMtuNeverSetOnWifiInterface();
     }
 
     @Test
@@ -17664,4 +17789,48 @@
         info.setExtraInfo("test_info");
         assertEquals("0;2;test_info", createDeliveryGroupKeyForConnectivityAction(info));
     }
+
+    @Test
+    public void testNetdWakeupAddInterfaceForWifiTransport() throws Exception {
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(false /* validated */);
+
+        final String expectedPrefix = makeNflogPrefix(WIFI_IFNAME,
+                mWiFiAgent.getNetwork().getNetworkHandle());
+        verify(mMockNetd).wakeupAddInterface(WIFI_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
+                PACKET_WAKEUP_MASK);
+    }
+
+    @Test
+    public void testNetdWakeupAddInterfaceForCellularTransport() throws Exception {
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName(MOBILE_IFNAME);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false /* validated */);
+
+        if (SdkLevel.isAtLeastU()) {
+            final String expectedPrefix = makeNflogPrefix(MOBILE_IFNAME,
+                    mCellAgent.getNetwork().getNetworkHandle());
+            verify(mMockNetd).wakeupAddInterface(MOBILE_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
+                    PACKET_WAKEUP_MASK);
+        } else {
+            verify(mMockNetd, never()).wakeupAddInterface(eq(MOBILE_IFNAME), anyString(), anyInt(),
+                    anyInt());
+        }
+    }
+
+    @Test
+    public void testNetdWakeupAddInterfaceForEthernetTransport() throws Exception {
+        final String ethernetIface = "eth42";
+
+        final LinkProperties ethLp = new LinkProperties();
+        ethLp.setInterfaceName(ethernetIface);
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET, ethLp);
+        mEthernetAgent.connect(false /* validated */);
+
+        verify(mMockNetd, never()).wakeupAddInterface(eq(ethernetIface), anyString(), anyInt(),
+                anyInt());
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 3eb1b26..9e0435d 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -38,7 +38,6 @@
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.net.INetd;
 import android.net.ISocketKeepaliveCallback;
 import android.net.KeepalivePacketData;
@@ -63,6 +62,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.connectivity.resources.R;
 import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
@@ -235,10 +235,8 @@
                 anyInt() /* pid */, anyInt() /* uid */);
         ConnectivityResources.setResourcesContextForTest(mCtx);
         final Resources mockResources = mock(Resources.class);
-        doReturn(MOCK_RESOURCE_ID).when(mockResources).getIdentifier(any() /* name */,
-                any() /* defType */, any() /* defPackage */);
         doReturn(new String[] { "0,3", "3,3" }).when(mockResources)
-                .getStringArray(MOCK_RESOURCE_ID);
+                .getStringArray(R.array.config_networkSupportedKeepaliveCount);
         doReturn(mockResources).when(mCtx).getResources();
         doReturn(mNetd).when(mDependencies).getNetd();
         doReturn(mAlarmManager).when(mDependencies).getAlarmManager(any());
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index b651c33..4158663 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -313,8 +313,7 @@
          * Stop clatd.
          */
         @Override
-        public void stopClatd(@NonNull String iface, @NonNull String pfx96, @NonNull String v4,
-                @NonNull String v6, int pid) throws IOException {
+        public void stopClatd(int pid) throws IOException {
             if (pid == -1) {
                 fail("unsupported arg: " + pid);
             }
@@ -479,8 +478,7 @@
                 eq((short) PRIO_CLAT), eq((short) ETH_P_IP));
         inOrder.verify(mEgressMap).deleteEntry(eq(EGRESS_KEY));
         inOrder.verify(mIngressMap).deleteEntry(eq(INGRESS_KEY));
-        inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
-                eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
+        inOrder.verify(mDeps).stopClatd(eq(CLATD_PID));
         inOrder.verify(mCookieTagMap).deleteEntry(eq(COOKIE_TAG_KEY));
         assertNull(coordinator.getClatdTrackerForTesting());
         inOrder.verifyNoMoreInteractions();
diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
index 0d371fa..e6c0c83 100644
--- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -34,7 +34,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityResources;
 import android.net.IDnsResolver;
 import android.net.INetd;
 import android.net.LinkProperties;
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
index b52e8a8..f19ba4f 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
@@ -21,10 +21,8 @@
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
-import android.net.ConnectivityResources
 import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
 import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
-import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
 import android.os.Build
 import android.os.Handler
 import android.os.test.TestLooper
@@ -37,6 +35,7 @@
 import com.android.connectivity.resources.R
 import com.android.internal.util.test.FakeSettingsProvider
 import com.android.modules.utils.build.SdkLevel
+import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.After
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
index 744c020..4c82c76 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
@@ -1,7 +1,6 @@
 package com.android.server.connectivity
 
 import android.content.res.Resources
-import android.net.ConnectivityResources
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
 import android.provider.DeviceConfig.OnPropertiesChangedListener
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index f4b6464..d667662 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -41,6 +42,8 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
+import libcore.util.EmptyArray;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,7 +63,8 @@
     private static final String EXAMPLE_IPV4 = "192.0.2.1";
     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
 
-    private static final long NET_HANDLE = new Network(5391).getNetworkHandle();
+    private static final Network TEST_WIFI_NETWORK = new Network(5391);
+    private static final Network TEST_CELL_NETWORK = new Network(5832);
 
     private static final byte[] MAC_ADDR =
             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
@@ -78,6 +82,8 @@
     public void setUp() {
         mCm = mock(ConnectivityManager.class);
         mService = new NetdEventListenerService(mCm);
+        doReturn(CAPABILITIES_WIFI).when(mCm).getNetworkCapabilities(TEST_WIFI_NETWORK);
+        doReturn(CAPABILITIES_CELL).when(mCm).getNetworkCapabilities(TEST_CELL_NETWORK);
     }
 
     @Test
@@ -111,19 +117,25 @@
         wakeupEvent(iface, uids[5], v4, tcp, mac, srcIp, dstIp, sport, dport, now);
         wakeupEvent(iface, uids[6], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent(iface, uids[7], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
-        wakeupEvent(iface, uids[8], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
+        wakeupEvent("rmnet0", uids[8], v6, udp, EmptyArray.BYTE, srcIp6, dstIp6, sport, dport, now,
+                TEST_CELL_NETWORK);
 
         String[] events2 = remove(listNetdEvent(), baseline);
-        int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
+        int expectedLength2 = uids.length + 2; // +2 for the WakeupStats headers
         assertEquals(expectedLength2, events2.length);
+
         assertStringContains(events2[0], "WakeupStats");
-        assertStringContains(events2[0], "wlan0");
-        assertStringContains(events2[0], "0x800");
+        assertStringContains(events2[0], "rmnet0");
         assertStringContains(events2[0], "0x86dd");
+
+        assertStringContains(events2[1], "WakeupStats");
+        assertStringContains(events2[1], "wlan0");
+        assertStringContains(events2[1], "0x800");
+        assertStringContains(events2[1], "0x86dd");
         for (int i = 0; i < uids.length; i++) {
-            String got = events2[i+1];
+            String got = events2[i + 2];
             assertStringContains(got, "WakeupEvent");
-            assertStringContains(got, "wlan0");
+            assertStringContains(got, ((i == 8) ? "rmnet0" : "wlan0"));
             assertStringContains(got, "uid: " + uids[i]);
         }
 
@@ -134,11 +146,13 @@
         }
 
         String[] events3 = remove(listNetdEvent(), baseline);
-        int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
+        int expectedLength3 = BUFFER_LENGTH + 2; // +2 for the WakeupStats headers
         assertEquals(expectedLength3, events3.length);
-        assertStringContains(events2[0], "WakeupStats");
-        assertStringContains(events2[0], "wlan0");
-        for (int i = 1; i < expectedLength3; i++) {
+        assertStringContains(events3[0], "WakeupStats");
+        assertStringContains(events3[0], "rmnet0");
+        assertStringContains(events3[1], "WakeupStats");
+        assertStringContains(events3[1], "wlan0");
+        for (int i = 2; i < expectedLength3; i++) {
             String got = events3[i];
             assertStringContains(got, "WakeupEvent");
             assertStringContains(got, "wlan0");
@@ -173,19 +187,24 @@
         final int icmp6 = 58;
 
         wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
-        wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, now);
-        wakeupEvent("rmnet0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 10008, v4, tcp, EmptyArray.BYTE, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
-        wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 10004, v4, udp, mac, srcIp, dstIp, sport, dport, now);
         wakeupEvent("wlan0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
-        wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
-        wakeupEvent("rmnet0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
+        wakeupEvent("rmnet0", 1000, v6, tcp, null, srcIp6, dstIp6, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 1010, v4, udp, mac, srcIp, dstIp, sport, dport, now);
 
         String got = flushStatistics();
@@ -214,7 +233,7 @@
                 "    >",
                 "    l2_broadcast_count: 0",
                 "    l2_multicast_count: 0",
-                "    l2_unicast_count: 5",
+                "    l2_unicast_count: 3",
                 "    no_uid_wakeups: 0",
                 "    non_application_wakeups: 0",
                 "    root_wakeups: 0",
@@ -499,8 +518,13 @@
     }
 
     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
-            String dstIp, int sport, int dport, long now) throws Exception {
-        String prefix = NET_HANDLE + ":" + iface;
+            String dstIp, int sport, int dport, long now) {
+        wakeupEvent(iface, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now, TEST_WIFI_NETWORK);
+    }
+
+    void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
+            String dstIp, int sport, int dport, long now, Network network) {
+        String prefix = network.getNetworkHandle() + ":" + iface;
         mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index e038c44..a27a0bf 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -51,7 +51,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.os.Build;
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 2926c9a..395e2bb 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -142,6 +142,7 @@
 import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.ipsec.ike.exceptions.IkeTimeoutException;
+import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -1563,6 +1564,11 @@
     }
 
     private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception {
+        return triggerOnAvailableAndGetCallback(new NetworkCapabilities.Builder().build());
+    }
+
+    private NetworkCallback triggerOnAvailableAndGetCallback(
+            @NonNull final NetworkCapabilities caps) throws Exception {
         final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
                 ArgumentCaptor.forClass(NetworkCallback.class);
         verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
@@ -1579,7 +1585,7 @@
         // if NetworkCapabilities and LinkProperties of underlying network will be sent/cleared or
         // not.
         // See verifyVpnManagerEvent().
-        cb.onCapabilitiesChanged(TEST_NETWORK, new NetworkCapabilities());
+        cb.onCapabilitiesChanged(TEST_NETWORK, caps);
         cb.onLinkPropertiesChanged(TEST_NETWORK, new LinkProperties());
         return cb;
     }
@@ -1903,12 +1909,15 @@
 
     private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
             IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
-        return verifySetupPlatformVpn(vpnProfile, ikeConfig, mtuSupportsIpv6,
-                false /* areLongLivedTcpConnectionsExpensive */);
+        return verifySetupPlatformVpn(vpnProfile, ikeConfig,
+                new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                mtuSupportsIpv6, false /* areLongLivedTcpConnectionsExpensive */);
     }
 
     private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
-            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6,
+            IkeSessionConfiguration ikeConfig,
+            @NonNull final NetworkCapabilities underlyingNetworkCaps,
+            boolean mtuSupportsIpv6,
             boolean areLongLivedTcpConnectionsExpensive) throws Exception {
         if (!mtuSupportsIpv6) {
             doReturn(IPV6_MIN_MTU - 1).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(),
@@ -1925,7 +1934,7 @@
                 .thenReturn(vpnProfile.encode());
 
         vpn.startVpnProfile(TEST_VPN_PKG);
-        final NetworkCallback nwCb = triggerOnAvailableAndGetCallback();
+        final NetworkCallback nwCb = triggerOnAvailableAndGetCallback(underlyingNetworkCaps);
         verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
         reset(mExecutor);
 
@@ -2079,15 +2088,16 @@
         doTestMigrateIkeSession(ikeProfile.toVpnProfile(),
                 expectedKeepalive,
                 ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */);
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                new NetworkCapabilities.Builder().build());
     }
 
-    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+    private Ikev2VpnProfile makeIkeV2VpnProfile(
             boolean isAutomaticIpVersionSelectionEnabled,
             boolean isAutomaticNattKeepaliveTimerEnabled,
             int keepaliveInProfile,
             int ipVersionInProfile,
-            int encapTypeInProfile) throws Exception {
+            int encapTypeInProfile) {
         // TODO: Update helper function in IkeSessionTestUtils to support building IkeSessionParams
         // with IP version and encap type when mainline-prod branch support these two APIs.
         final IkeSessionParams params = getTestIkeSessionParams(true /* testIpv6 */,
@@ -2099,12 +2109,40 @@
 
         final IkeTunnelConnectionParams tunnelParams =
                 new IkeTunnelConnectionParams(ikeSessionParams, CHILD_PARAMS);
-        final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams)
+        return new Ikev2VpnProfile.Builder(tunnelParams)
                 .setBypassable(true)
                 .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
                 .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
                 .build();
+    }
 
+    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile) throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(isAutomaticIpVersionSelectionEnabled,
+                isAutomaticNattKeepaliveTimerEnabled, keepaliveInProfile, ipVersionInProfile,
+                encapTypeInProfile, new NetworkCapabilities.Builder().build());
+    }
+
+    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile,
+            @NonNull final NetworkCapabilities nc) throws Exception {
+        final Ikev2VpnProfile ikeProfile = makeIkeV2VpnProfile(
+                isAutomaticIpVersionSelectionEnabled,
+                isAutomaticNattKeepaliveTimerEnabled,
+                keepaliveInProfile,
+                ipVersionInProfile,
+                encapTypeInProfile);
+
+        final IkeSessionParams ikeSessionParams =
+                ikeProfile.getIkeTunnelConnectionParams().getIkeSessionParams();
         final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
                 ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
                 : ikeSessionParams.getNattKeepAliveDelaySeconds();
@@ -2115,22 +2153,48 @@
                 ? ESP_ENCAP_TYPE_AUTO
                 : ikeSessionParams.getEncapType();
         doTestMigrateIkeSession(ikeProfile.toVpnProfile(), expectedKeepalive,
-                expectedIpVersion, expectedEncapType);
+                expectedIpVersion, expectedEncapType, nc);
     }
 
-    private void doTestMigrateIkeSession(VpnProfile profile,
-            int expectedKeepalive, int expectedIpVersion, int expectedEncapType) throws Exception {
+    @Test
+    public void doTestMigrateIkeSession_Vcn() throws Exception {
+        final int expectedKeepalive = 2097; // Any unlikely number will do
+        final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive))
+                .build();
+        final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                234 /* keepaliveInProfile */, // Should be ignored, any value will do
+                ESP_IP_VERSION_IPV4, // Should be ignored
+                ESP_ENCAP_TYPE_UDP // Should be ignored
+        );
+        doTestMigrateIkeSession(
+                ikev2VpnProfile.toVpnProfile(),
+                expectedKeepalive,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                vcnNc);
+    }
+
+    private void doTestMigrateIkeSession(
+            @NonNull final VpnProfile profile,
+            final int expectedKeepalive,
+            final int expectedIpVersion,
+            final int expectedEncapType,
+            @NonNull final NetworkCapabilities caps) throws Exception {
         final PlatformVpnSnapshot vpnSnapShot =
                 verifySetupPlatformVpn(profile,
                         createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        caps /* underlying network capabilities */,
                         false /* mtuSupportsIpv6 */,
                         expectedKeepalive < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC);
         // Simulate a new network coming up
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
         verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
 
-        vpnSnapShot.nwCb.onCapabilitiesChanged(
-                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, caps);
         // Verify MOBIKE is triggered
         verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
                 expectedIpVersion, expectedEncapType, expectedKeepalive);
@@ -2156,6 +2220,7 @@
         final PlatformVpnSnapshot vpnSnapShot =
                 verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
                         createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
                         hasV6 /* mtuSupportsIpv6 */,
                         false /* areLongLivedTcpConnectionsExpensive */);
         reset(mExecutor);
@@ -2343,6 +2408,7 @@
         final PlatformVpnSnapshot vpnSnapShot =
                 verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
                         createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
                         false /* mtuSupportsIpv6 */,
                         true /* areLongLivedTcpConnectionsExpensive */);
 
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index a917361..4b495cd 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -47,6 +47,8 @@
 
 private const val SERVICE_ID_1 = 1
 private const val SERVICE_ID_2 = 2
+private const val LONG_SERVICE_ID_1 = 3
+private const val LONG_SERVICE_ID_2 = 4
 private const val TIMEOUT_MS = 10_000L
 private val TEST_ADDR = parseNumericAddress("2001:db8::123")
 private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
@@ -56,16 +58,30 @@
 
 private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
     port = 12345
-    host = TEST_ADDR
+    hostAddresses = listOf(TEST_ADDR)
     network = TEST_NETWORK_1
 }
 
+private val LONG_SERVICE_1 =
+    NsdServiceInfo("a".repeat(48) + "TestServiceName", "_longadvertisertest._tcp").apply {
+    port = 12345
+    hostAddresses = listOf(TEST_ADDR)
+    network = TEST_NETWORK_1
+    }
+
 private val ALL_NETWORKS_SERVICE = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
     port = 12345
-    host = TEST_ADDR
+    hostAddresses = listOf(TEST_ADDR)
     network = null
 }
 
+private val LONG_ALL_NETWORKS_SERVICE =
+    NsdServiceInfo("a".repeat(48) + "TestServiceName", "_longadvertisertest._tcp").apply {
+        port = 12345
+        hostAddresses = listOf(TEST_ADDR)
+        network = null
+    }
+
 @RunWith(DevSdkIgnoreRunner::class)
 @IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class MdnsAdvertiserTest {
@@ -191,6 +207,9 @@
         verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
         val allNetSocketCb = allNetSocketCbCaptor.value
 
+        postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1) }
+        postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE) }
+
         // Callbacks for matching network and all networks both get the socket
         postSync {
             oneNetSocketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR))
@@ -200,10 +219,18 @@
         val expectedRenamed = NsdServiceInfo(
                 "${ALL_NETWORKS_SERVICE.serviceName} (2)", ALL_NETWORKS_SERVICE.serviceType).apply {
             port = ALL_NETWORKS_SERVICE.port
-            host = ALL_NETWORKS_SERVICE.host
+            hostAddresses = ALL_NETWORKS_SERVICE.hostAddresses
             network = ALL_NETWORKS_SERVICE.network
         }
 
+        val expectedLongRenamed = NsdServiceInfo(
+            "${LONG_ALL_NETWORKS_SERVICE.serviceName.dropLast(4)} (2)",
+            LONG_ALL_NETWORKS_SERVICE.serviceType).apply {
+            port = LONG_ALL_NETWORKS_SERVICE.port
+            hostAddresses = LONG_ALL_NETWORKS_SERVICE.hostAddresses
+            network = LONG_ALL_NETWORKS_SERVICE.network
+        }
+
         val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
         verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
                 eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME)
@@ -212,6 +239,10 @@
                 argThat { it.matches(SERVICE_1) })
         verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
                 argThat { it.matches(expectedRenamed) })
+        verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
+                argThat { it.matches(LONG_SERVICE_1) })
+        verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
+            argThat { it.matches(expectedLongRenamed) })
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
         postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
@@ -251,7 +282,7 @@
     return Objects.equals(serviceName, other.serviceName) &&
             Objects.equals(serviceType, other.serviceType) &&
             Objects.equals(attributes, other.attributes) &&
-            Objects.equals(host, other.host) &&
+            Objects.equals(hostAddresses, other.hostAddresses) &&
             port == other.port &&
             Objects.equals(network, other.network)
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
new file mode 100644
index 0000000..f091eea
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -0,0 +1,136 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.net.Network
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertNotNull
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+private const val SERVICE_NAME_1 = "service-instance-1"
+private const val SERVICE_NAME_2 = "service-instance-2"
+private const val SERVICE_TYPE_1 = "_test1._tcp.local"
+private const val SERVICE_TYPE_2 = "_test2._tcp.local"
+private const val INTERFACE_INDEX = 999
+private const val DEFAULT_TIMEOUT_MS = 2000L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsServiceCacheTest {
+    private val network = mock(Network::class.java)
+    private val thread = HandlerThread(MdnsServiceCacheTest::class.simpleName)
+    private val handler by lazy {
+        Handler(thread.looper)
+    }
+    private val serviceCache by lazy {
+        MdnsServiceCache(thread.looper)
+    }
+
+    @Before
+    fun setUp() {
+        thread.start()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+    }
+
+    private fun <T> runningOnHandlerAndReturn(functor: (() -> T)): T {
+        val future = CompletableFuture<T>()
+        handler.post {
+            future.complete(functor())
+        }
+        return future.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+    }
+
+    private fun addOrUpdateService(serviceType: String, network: Network, service: MdnsResponse):
+            Unit = runningOnHandlerAndReturn {
+        serviceCache.addOrUpdateService(serviceType, network, service) }
+
+    private fun removeService(serviceName: String, serviceType: String, network: Network):
+            Unit = runningOnHandlerAndReturn {
+        serviceCache.removeService(serviceName, serviceType, network) }
+
+    private fun getService(serviceName: String, serviceType: String, network: Network):
+            MdnsResponse? = runningOnHandlerAndReturn {
+        serviceCache.getCachedService(serviceName, serviceType, network) }
+
+    private fun getServices(serviceType: String, network: Network): List<MdnsResponse> =
+        runningOnHandlerAndReturn { serviceCache.getCachedServices(serviceType, network) }
+
+    @Test
+    fun testAddAndRemoveService() {
+        addOrUpdateService(SERVICE_TYPE_1, network, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+        var response = getService(SERVICE_NAME_1, SERVICE_TYPE_1, network)
+        assertNotNull(response)
+        assertEquals(SERVICE_NAME_1, response.serviceInstanceName)
+        removeService(SERVICE_NAME_1, SERVICE_TYPE_1, network)
+        response = getService(SERVICE_NAME_1, SERVICE_TYPE_1, network)
+        assertNull(response)
+    }
+
+    @Test
+    fun testGetCachedServices_multipleServiceTypes() {
+        addOrUpdateService(SERVICE_TYPE_1, network, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+        addOrUpdateService(SERVICE_TYPE_1, network, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
+        addOrUpdateService(SERVICE_TYPE_2, network, createResponse(SERVICE_NAME_2, SERVICE_TYPE_2))
+
+        val responses1 = getServices(SERVICE_TYPE_1, network)
+        assertEquals(2, responses1.size)
+        assertTrue(responses1.stream().anyMatch { response ->
+            response.serviceInstanceName == SERVICE_NAME_1
+        })
+        assertTrue(responses1.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_2
+        })
+        val responses2 = getServices(SERVICE_TYPE_2, network)
+        assertEquals(1, responses2.size)
+        assertTrue(responses2.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_2
+        })
+
+        removeService(SERVICE_NAME_2, SERVICE_TYPE_1, network)
+        val responses3 = getServices(SERVICE_TYPE_1, network)
+        assertEquals(1, responses3.size)
+        assertTrue(responses3.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_1
+        })
+        val responses4 = getServices(SERVICE_TYPE_2, network)
+        assertEquals(1, responses4.size)
+        assertTrue(responses4.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_2
+        })
+    }
+
+    private fun createResponse(serviceInstanceName: String, serviceType: String) = MdnsResponse(
+        0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
+            INTERFACE_INDEX, network)
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 2d73c98..6f3322b 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -112,7 +112,7 @@
     private NetworkCallback mNetworkCallback;
     private TetheringEventCallback mTetheringEventCallback;
 
-    private TestNetLinkMonitor mTestSocketNetLinkMonitor;
+    private TestNetlinkMonitor mTestSocketNetLinkMonitor;
     @Before
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
@@ -147,7 +147,7 @@
         doReturn(mTestSocketNetLinkMonitor).when(mDeps).createSocketNetlinkMonitor(any(), any(),
                 any());
         doAnswer(inv -> {
-            mTestSocketNetLinkMonitor = new TestNetLinkMonitor(inv.getArgument(0),
+            mTestSocketNetLinkMonitor = new TestNetlinkMonitor(inv.getArgument(0),
                     inv.getArgument(1),
                     inv.getArgument(2));
             return mTestSocketNetLinkMonitor;
@@ -174,8 +174,8 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
     }
 
-    private static class TestNetLinkMonitor extends SocketNetlinkMonitor {
-        TestNetLinkMonitor(@NonNull Handler handler,
+    private static class TestNetlinkMonitor extends SocketNetlinkMonitor {
+        TestNetlinkMonitor(@NonNull Handler handler,
                 @NonNull SharedLog log,
                 @Nullable MdnsSocketProvider.NetLinkMonitorCallBack cb) {
             super(handler, log, cb);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
new file mode 100644
index 0000000..61c3a70
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.server.connectivity.mdns.util
+
+import android.os.Build
+import com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase
+import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase
+import com.android.server.connectivity.mdns.util.MdnsUtils.truncateServiceName
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsUtilsTest {
+    @Test
+    fun testToDnsLowerCase() {
+        assertEquals("test", toDnsLowerCase("TEST"))
+        assertEquals("test", toDnsLowerCase("TeSt"))
+        assertEquals("test", toDnsLowerCase("test"))
+        assertEquals("tÉst", toDnsLowerCase("TÉST"))
+        assertEquals("ţést", toDnsLowerCase("ţést"))
+        // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
+        // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
+        assertEquals("test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+                toDnsLowerCase("Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "))
+        // Also test some characters where the first surrogate is not \ud800
+        assertEquals("test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
+                toDnsLowerCase("Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                        "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"))
+    }
+
+    @Test
+    fun testEqualsIgnoreDnsCase() {
+        assertTrue(equalsIgnoreDnsCase("TEST", "Test"))
+        assertTrue(equalsIgnoreDnsCase("TEST", "test"))
+        assertTrue(equalsIgnoreDnsCase("test", "TeSt"))
+        assertTrue(equalsIgnoreDnsCase("Tést", "tést"))
+        assertFalse(equalsIgnoreDnsCase("ŢÉST", "ţést"))
+        // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
+        // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
+        assertTrue(equalsIgnoreDnsCase("test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+                "Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "))
+        // Also test some characters where the first surrogate is not \ud800
+        assertTrue(equalsIgnoreDnsCase("test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
+                "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                        "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"))
+    }
+
+    @Test
+    fun testTruncateServiceName() {
+        assertEquals(truncateServiceName("测试abcde", 7), "测试a")
+        assertEquals(truncateServiceName("测试abcde", 100), "测试abcde")
+    }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 04163fd..99f6d63 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -96,7 +96,6 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.net.ConnectivityResources;
 import android.net.DataUsageRequest;
 import android.net.INetd;
 import android.net.INetworkStatsSession;
@@ -145,6 +144,7 @@
 import com.android.net.module.util.bpf.CookieTagMapKey;
 import com.android.net.module.util.bpf.CookieTagMapValue;
 import com.android.server.BpfNetMaps;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.net.NetworkStatsService.AlertObserver;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;