Merge "DscpPolicyTest Check Kernel Version"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 302c0b3..1b2c0ed 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -32,6 +32,9 @@
     },
     {
       "name": "libnetworkstats_test"
+    },
+    {
+      "name": "FrameworksNetIntegrationTests"
     }
   ],
   "postsubmit": [
@@ -55,6 +58,9 @@
     },
     {
       "name": "libnetworkstats_test"
+    },
+    {
+      "name": "FrameworksNetDeflakeTest"
     }
   ],
   "mainline-presubmit": [
@@ -88,6 +94,35 @@
       "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
       "keywords": ["sim"]
     },
+    // TODO: move to mainline-presubmit when known green.
+    // Test with APK modules only, in cases where APEX is not supported, or the other modules were simply not updated
+    {
+      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+        }
+      ]
+    },
+    // TODO: move to mainline-presubmit when known green.
+    // Test with connectivity/tethering module only, to catch integration issues with older versions of other modules
+    {
+      "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
     {
       "name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     },
@@ -96,6 +131,23 @@
       "name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     }
   ],
+  "auto-postsubmit": [
+    // Test tag for automotive targets. These are only running in postsubmit so as to harden the
+    // automotive targets to avoid introducing additional test flake and build time. The plan for
+    // presubmit testing for auto is to augment the existing tests to cover auto use cases as well.
+    // Additionally, this tag is used in targeted test suites to limit resource usage on the test
+    // infra during the hardening phase.
+    // TODO: this tag to be removed once the above is no longer an issue.
+    {
+      "name": "FrameworksNetTests"
+    },
+    {
+      "name": "FrameworksNetIntegrationTests"
+    },
+    {
+      "name": "FrameworksNetDeflakeTest"
+    }
+  ],
   "imports": [
     {
       "path": "frameworks/base/core/java/android/net"
@@ -110,9 +162,6 @@
       "path": "packages/modules/CaptivePortalLogin"
     },
     {
-      "path": "packages/modules/Connectivity"
-    },
-    {
       "path": "packages/modules/Connectivity/Tethering"
     }
   ]
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index ae96e8c..46fd50f 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -179,7 +179,7 @@
     // The permission configuration *must* be included to ensure security of the device
     required: [
         "NetworkPermissionConfig",
-        "privapp_whitelist_com.android.networkstack.tethering",
+        "privapp_allowlist_com.android.tethering",
     ],
     apex_available: ["com.android.tethering"],
     lint: { strict_updatability_linting: true },
@@ -199,7 +199,7 @@
     // The permission configuration *must* be included to ensure security of the device
     required: [
         "NetworkPermissionConfig",
-        "privapp_whitelist_com.android.networkstack.tethering",
+        "privapp_allowlist_com.android.tethering",
     ],
     apex_available: ["com.android.tethering"],
     lint: { strict_updatability_linting: true },
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index ea3f8d6..1a4ba9d 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -74,7 +74,10 @@
     apps: [
         "ServiceConnectivityResources",
     ],
-    prebuilts: ["current_sdkinfo"],
+    prebuilts: [
+        "current_sdkinfo",
+        "privapp_allowlist_com.android.tethering",
+    ],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
     // Indicates that pre-installed version of this apex can be compressed.
diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp
new file mode 100644
index 0000000..ac9ec65
--- /dev/null
+++ b/Tethering/apex/permissions/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"],
+}
+
+prebuilt_etc {
+    name: "privapp_allowlist_com.android.tethering",
+    sub_dir: "permissions",
+    filename: "permissions.xml",
+    src: "permissions.xml",
+    installable: false,
+}
\ No newline at end of file
diff --git a/Tethering/apex/permissions/OWNERS b/Tethering/apex/permissions/OWNERS
new file mode 100644
index 0000000..8b7e2e5
--- /dev/null
+++ b/Tethering/apex/permissions/OWNERS
@@ -0,0 +1,2 @@
+per-file *.xml,OWNERS = set noparent
+per-file *.xml,OWNERS = file:platform/frameworks/base:/data/etc/OWNERS
diff --git a/Tethering/apex/permissions/permissions.xml b/Tethering/apex/permissions/permissions.xml
new file mode 100644
index 0000000..f26a961
--- /dev/null
+++ b/Tethering/apex/permissions/permissions.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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
+-->
+
+<permissions>
+    <privapp-permissions package="com.android.networkstack.tethering">
+        <permission name="android.permission.BLUETOOTH_PRIVILEGED" />
+        <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.UPDATE_DEVICE_STATS"/>
+      </privapp-permissions>
+</permissions>
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 26040a2..22d2c5d 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -27,14 +27,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.TetherStatsValue;
 
-import java.util.function.BiConsumer;
-
 /**
  * Bpf coordinator class for API shims.
  */
@@ -164,7 +163,7 @@
 
     @Override
     public void tetherOffloadRuleForEach(boolean downstream,
-            @NonNull BiConsumer<Tether4Key, Tether4Value> action) {
+            @NonNull ThrowingBiConsumer<Tether4Key, Tether4Value> action) {
         /* no op */
     }
 
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index e3b1539..5afb862 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -30,6 +30,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
@@ -47,7 +48,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.util.function.BiConsumer;
 
 /**
  * Bpf coordinator class for API shims.
@@ -410,7 +410,7 @@
 
     @Override
     public void tetherOffloadRuleForEach(boolean downstream,
-            @NonNull BiConsumer<Tether4Key, Tether4Value> action) {
+            @NonNull ThrowingBiConsumer<Tether4Key, Tether4Value> action) {
         if (!isInitialized()) return;
 
         try {
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index d663968..915e210 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -22,14 +22,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.TetherStatsValue;
 
-import java.util.function.BiConsumer;
-
 /**
  * Bpf coordinator class for API shims.
  */
@@ -163,7 +162,7 @@
      */
     @Nullable
     public abstract void tetherOffloadRuleForEach(boolean downstream,
-            @NonNull BiConsumer<Tether4Key, Tether4Value> action);
+            @NonNull ThrowingBiConsumer<Tether4Key, Tether4Value> action);
 
     /**
      * Whether there is currently any IPv4 rule on the specified upstream.
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 40956f7..6550de2 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -52,6 +52,7 @@
 import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Base64;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -118,6 +119,9 @@
     private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
     private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
 
+    // Using "," as a separator is safe because base64 characters are [0-9a-zA-Z/=+].
+    private static final String DUMP_BASE64_DELIMITER = ",";
+
     /** The names of all the BPF counters defined in bpf_tethering.h. */
     public static final String[] sBpfCounterNames = getBpfCounterNames();
 
@@ -1068,6 +1072,42 @@
         }
     }
 
+    private String ipv4RuleToBase64String(Tether4Key key, Tether4Value value) {
+        final byte[] keyBytes = key.writeToBytes();
+        final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
+                .replace("\n", "");
+        final byte[] valueBytes = value.writeToBytes();
+        final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT)
+                .replace("\n", "");
+
+        return keyBase64Str + DUMP_BASE64_DELIMITER + valueBase64Str;
+    }
+
+    private void dumpRawIpv4ForwardingRuleMap(
+            BpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
+        if (map == null) {
+            pw.println("No IPv4 support");
+            return;
+        }
+        if (map.isEmpty()) {
+            pw.println("No rules");
+            return;
+        }
+        map.forEach((k, v) -> pw.println(ipv4RuleToBase64String(k, v)));
+    }
+
+    /**
+     * Dump raw BPF map in base64 encoded strings. For test only.
+     */
+    public void dumpRawMap(@NonNull IndentingPrintWriter pw) {
+        try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+            // TODO: dump downstream map.
+            dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
+        } catch (ErrnoException e) {
+            pw.println("Error dumping IPv4 map: " + e);
+        }
+    }
+
     private String l4protoToString(int proto) {
         if (proto == OsConstants.IPPROTO_TCP) {
             return "tcp";
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index db9a64f..301a682 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2479,6 +2479,13 @@
         @SuppressWarnings("resource") final IndentingPrintWriter pw = new IndentingPrintWriter(
                 writer, "  ");
 
+        // Used for testing instead of human debug.
+        // TODO: add options to choose which map to dump.
+        if (argsContain(args, "bpfRawMap")) {
+            mBpfCoordinator.dumpRawMap(pw);
+            return;
+        }
+
         if (argsContain(args, "bpf")) {
             dumpBpf(pw);
             return;
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index d2188d1..6eaf68b 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -30,6 +30,7 @@
         "androidx.test.rules",
         "mockito-target-extended-minus-junit4",
         "net-tests-utils",
+        "net-utils-device-common-bpf",
         "testables",
     ],
     libs: [
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 8bf1a2b..705d187 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.TETHER_PRIVILEGED;
@@ -53,11 +54,15 @@
 import android.net.TetheringManager.TetheringEventCallback;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringTester.TetheredDevice;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -67,17 +72,24 @@
 
 import com.android.net.module.util.PacketBuilder;
 import com.android.net.module.util.Struct;
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.structs.EthernetHeader;
 import com.android.net.module.util.structs.Icmpv6Header;
 import com.android.net.module.util.structs.Ipv4Header;
 import com.android.net.module.util.structs.Ipv6Header;
 import com.android.net.module.util.structs.UdpHeader;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DumpTestUtils;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
 import com.android.testutils.TestNetworkTracker;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -88,9 +100,12 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -101,10 +116,17 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class EthernetTetheringTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
 
     private static final String TAG = EthernetTetheringTest.class.getSimpleName();
     private static final int TIMEOUT_MS = 5000;
     private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
+    private static final int DUMP_POLLING_MAX_RETRY = 100;
+    private static final int DUMP_POLLING_INTERVAL_MS = 50;
+    // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
+    // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
+    private static final int UDP_STREAM_TS_MS = 2000;
     private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
     private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
     private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
@@ -112,6 +134,10 @@
     private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
             ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
 
+    private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+    private static final String BASE64_DELIMITER = ",";
+    private static final String LINE_DELIMITER = "\\n";
+
     private final Context mContext = InstrumentationRegistry.getContext();
     private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
     private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
@@ -136,10 +162,11 @@
         // Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive
         // tethered client callbacks. The restricted networks permission is needed to ensure that
         // EthernetManager#isAvailable will correctly return true on devices where Ethernet is
-        // marked restricted, like cuttlefish.
+        // marked restricted, like cuttlefish. The dump permission is needed to verify bpf related
+        // functions via dumpsys output.
         mUiAutomation.adoptShellPermissionIdentity(
                 MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
-                CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
         mRunTests = mTm.isTetheringSupported() && mEm != null;
         assumeTrue(mRunTests);
 
@@ -747,12 +774,15 @@
     private static final byte TYPE_OF_SERVICE = 0;
     private static final short ID = 27149;
     private static final short ID2 = 27150;
+    private static final short ID3 = 27151;
     private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
     private static final byte TIME_TO_LIVE = (byte) 0x40;
     private static final ByteBuffer PAYLOAD =
             ByteBuffer.wrap(new byte[] { (byte) 0x12, (byte) 0x34 });
     private static final ByteBuffer PAYLOAD2 =
             ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
+    private static final ByteBuffer PAYLOAD3 =
+            ByteBuffer.wrap(new byte[] { (byte) 0x9a, (byte) 0xbc });
 
     private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther,
             @NonNull final ByteBuffer payload) {
@@ -830,7 +860,8 @@
         return false;
     }
 
-    private void runUdp4Test(TetheringTester tester, RemoteResponder remote) throws Exception {
+    private void runUdp4Test(TetheringTester tester, RemoteResponder remote, boolean usingBpf)
+            throws Exception {
         final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
                 "1:2:3:4:5:6"));
 
@@ -861,10 +892,51 @@
             Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
             return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
         });
+
+        if (usingBpf) {
+            // Send second UDP packet in original direction.
+            // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
+            // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
+            // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
+            // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
+            // and apply ASSURED flag.
+            // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
+            // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
+            Thread.sleep(UDP_STREAM_TS_MS);
+            final ByteBuffer originalPacket2 = buildUdpv4Packet(tethered.macAddr,
+                    tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
+                    REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */,
+                    REMOTE_PORT /*dstPort */, PAYLOAD3 /* payload */);
+            tester.verifyUpload(remote, originalPacket2, p -> {
+                Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+                return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
+            });
+
+            final HashMap<Tether4Key, Tether4Value> upstreamMap = pollIpv4UpstreamMapFromDump();
+            assertNotNull(upstreamMap);
+            assertEquals(1, upstreamMap.size());
+
+            final Map.Entry<Tether4Key, Tether4Value> rule =
+                    upstreamMap.entrySet().iterator().next();
+
+            final Tether4Key key = rule.getKey();
+            assertEquals(IPPROTO_UDP, key.l4proto);
+            assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), key.src4));
+            assertEquals(LOCAL_PORT, key.srcPort);
+            assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), key.dst4));
+            assertEquals(REMOTE_PORT, key.dstPort);
+
+            final Tether4Value value = rule.getValue();
+            assertTrue(Arrays.equals(publicIp4Addr.getAddress(),
+                    InetAddress.getByAddress(value.src46).getAddress()));
+            assertEquals(LOCAL_PORT, value.srcPort);
+            assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
+                    InetAddress.getByAddress(value.dst46).getAddress()));
+            assertEquals(REMOTE_PORT, value.dstPort);
+        }
     }
 
-    @Test
-    public void testUdpV4() throws Exception {
+    void initializeTethering() throws Exception {
         assumeFalse(mEm.isAvailable());
 
         // MyTetheringEventCallback currently only support await first available upstream. Tethering
@@ -885,8 +957,75 @@
 
         mDownstreamReader = makePacketReader(mDownstreamIface);
         mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+    }
 
-        runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader));
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.Q)
+    public void testTetherUdpV4WithoutBpf() throws Exception {
+        initializeTethering();
+        runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
+                false /* usingBpf */);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testTetherUdpV4WithBpf() throws Exception {
+        initializeTethering();
+        runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
+                true /* usingBpf */);
+    }
+
+    @Nullable
+    private Pair<Tether4Key, Tether4Value> parseTether4KeyValue(@NonNull String dumpStr) {
+        Log.w(TAG, "Parsing string: " + dumpStr);
+
+        String[] keyValueStrs = dumpStr.split(BASE64_DELIMITER);
+        if (keyValueStrs.length != 2 /* key + value */) {
+            fail("The length is " + keyValueStrs.length + " but expect 2. "
+                    + "Split string(s): " + TextUtils.join(",", keyValueStrs));
+        }
+
+        final byte[] keyBytes = Base64.decode(keyValueStrs[0], Base64.DEFAULT);
+        Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
+        final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
+        keyByteBuffer.order(ByteOrder.nativeOrder());
+        final Tether4Key tether4Key = Struct.parse(Tether4Key.class, keyByteBuffer);
+        Log.w(TAG, "tether4Key: " + tether4Key);
+
+        final byte[] valueBytes = Base64.decode(keyValueStrs[1], Base64.DEFAULT);
+        Log.d(TAG, "valueBytes: " + dumpHexString(valueBytes));
+        final ByteBuffer valueByteBuffer = ByteBuffer.wrap(valueBytes);
+        valueByteBuffer.order(ByteOrder.nativeOrder());
+        final Tether4Value tether4Value = Struct.parse(Tether4Value.class, valueByteBuffer);
+        Log.w(TAG, "tether4Value: " + tether4Value);
+
+        return new Pair<>(tether4Key, tether4Value);
+    }
+
+    @NonNull
+    private HashMap<Tether4Key, Tether4Value> dumpIpv4UpstreamMap() throws Exception {
+        final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE,
+                DUMPSYS_TETHERING_RAWMAP_ARG);
+        final HashMap<Tether4Key, Tether4Value> map = new HashMap<>();
+
+        for (final String line : rawMapStr.split(LINE_DELIMITER)) {
+            final Pair<Tether4Key, Tether4Value> rule = parseTether4KeyValue(line.trim());
+            map.put(rule.first, rule.second);
+        }
+        return map;
+    }
+
+    @Nullable
+    private HashMap<Tether4Key, Tether4Value> pollIpv4UpstreamMapFromDump() throws Exception {
+        for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
+            final HashMap<Tether4Key, Tether4Value> map = dumpIpv4UpstreamMap();
+            if (!map.isEmpty()) return map;
+
+            Thread.sleep(DUMP_POLLING_INTERVAL_MS);
+        }
+
+        fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
+        return null;
     }
 
     private <T> List<T> toList(T... array) {
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index f464322..50a3eb2 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -128,7 +128,7 @@
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
-	"//packages/modules/IPsec/tests/iketests",
+        "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Nearby/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index fda1045..5579db6 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -10,6 +10,8 @@
     method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshots();
     method @Nullable public android.net.ProxyInfo getGlobalProxy();
     method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties redactLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 5246623..e8e1efa 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1625,16 +1625,45 @@
     }
 
     /**
-     * Get the {@link NetworkCapabilities} for the given {@link Network}.  This
-     * will return {@code null} if the network is unknown or if the |network| argument is null.
+     * Redact {@link LinkProperties} for a given package
      *
-     * This will remove any location sensitive data in {@link TransportInfo} embedded in
-     * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like
-     * {@link android.net.wifi.WifiInfo} contain location sensitive information. Retrieving
-     * this location sensitive information (subject to app's location permissions) will be
-     * noted by system. To include any location sensitive data in {@link TransportInfo},
-     * use a {@link NetworkCallback} with
-     * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag.
+     * Returns an instance of the given {@link LinkProperties} appropriately redacted to send to the
+     * given package, considering its permissions.
+     *
+     * @param lp A {@link LinkProperties} which will be redacted.
+     * @param uid The target uid.
+     * @param packageName The name of the package, for appops logging.
+     * @return A redacted {@link LinkProperties} which is appropriate to send to the given uid,
+     *         or null if the uid lacks the ACCESS_NETWORK_STATE permission.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
+    @Nullable
+    public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
+            @NonNull String packageName) {
+        try {
+            return mService.redactLinkPropertiesForPackage(
+                    lp, uid, packageName, getAttributionTag());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the {@link NetworkCapabilities} for the given {@link Network}, or null.
+     *
+     * This will remove any location sensitive data in the returned {@link NetworkCapabilities}.
+     * Some {@link TransportInfo} instances like {@link android.net.wifi.WifiInfo} contain location
+     * sensitive information. To retrieve this location sensitive information (subject to
+     * the caller's location permissions), use a {@link NetworkCallback} with the
+     * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag instead.
+     *
+     * This method returns {@code null} if the network is unknown or if the |network| argument
+     * is null.
      *
      * @param network The {@link Network} object identifying the network in question.
      * @return The {@link NetworkCapabilities} for the network, or {@code null}.
@@ -1651,6 +1680,38 @@
     }
 
     /**
+     * Redact {@link NetworkCapabilities} for a given package.
+     *
+     * Returns an instance of {@link NetworkCapabilities} that is appropriately redacted to send
+     * to the given package, considering its permissions. Calling this method will blame the UID for
+     * retrieving the device location if the passed capabilities contain location-sensitive
+     * information.
+     *
+     * @param nc A {@link NetworkCapabilities} instance which will be redacted.
+     * @param uid The target uid.
+     * @param packageName The name of the package, for appops logging.
+     * @return A redacted {@link NetworkCapabilities} which is appropriate to send to the given uid,
+     *         or null if the uid lacks the ACCESS_NETWORK_STATE permission.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
+    @Nullable
+    public NetworkCapabilities redactNetworkCapabilitiesForPackage(
+            @NonNull NetworkCapabilities nc,
+            int uid, @NonNull String packageName) {
+        try {
+            return mService.redactNetworkCapabilitiesForPackage(nc, uid, packageName,
+                    getAttributionTag());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets a URL that can be used for resolving whether a captive portal is present.
      * 1. This URL should respond with a 204 response to a GET request to indicate no captive
      *    portal is present.
@@ -3547,7 +3608,20 @@
          * @hide
          */
         public static final int FLAG_NONE = 0;
+
         /**
+         * Inclusion of this flag means location-sensitive redaction requests keeping location info.
+         *
+         * Some objects like {@link NetworkCapabilities} may contain location-sensitive information.
+         * Prior to Android 12, this information is always returned to apps holding the appropriate
+         * permission, possibly noting that the app has used location.
+         * <p>In Android 12 and above, by default the sent objects do not contain any location
+         * information, even if the app holds the necessary permissions, and the system does not
+         * take note of location usage by the app. Apps can request that location information is
+         * included, in which case the system will check location permission and the location
+         * toggle state, and take note of location usage by the app if any such information is
+         * returned.
+         *
          * Use this flag to include any location sensitive data in {@link NetworkCapabilities} sent
          * via {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}.
          * <p>
@@ -3564,8 +3638,7 @@
          * <li> Retrieving this location sensitive information (subject to app's location
          * permissions) will be noted by system. </li>
          * <li> Without this flag any {@link NetworkCapabilities} provided via the callback does
-         * not include location sensitive info.
-         * </p>
+         * not include location sensitive information.
          */
         // Note: Some existing fields which are location sensitive may still be included without
         // this flag if the app targets SDK < S (to maintain backwards compatibility).
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index df4663f..23a3850 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -76,10 +76,15 @@
     LinkProperties getActiveLinkProperties();
     LinkProperties getLinkPropertiesForType(int networkType);
     LinkProperties getLinkProperties(in Network network);
+    LinkProperties redactLinkPropertiesForPackage(in LinkProperties lp, int uid, String packageName,
+            String callingAttributionTag);
 
     NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName,
             String callingAttributionTag);
 
+    NetworkCapabilities redactNetworkCapabilitiesForPackage(in NetworkCapabilities nc, int uid,
+            String callingPackageName, String callingAttributionTag);
+
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     NetworkState[] getAllNetworkState();
 
diff --git a/framework/src/android/net/KeepalivePacketData.java b/framework/src/android/net/KeepalivePacketData.java
index 5877f1f..f47cc5c 100644
--- a/framework/src/android/net/KeepalivePacketData.java
+++ b/framework/src/android/net/KeepalivePacketData.java
@@ -116,4 +116,13 @@
         return mPacket.clone();
     }
 
+    @Override
+    public String toString() {
+        return "KeepalivePacketData[srcAddress=" + mSrcAddress
+                + ", dstAddress=" + mDstAddress
+                + ", srcPort=" + mSrcPort
+                + ", dstPort=" + mDstPort
+                + ", packet.length=" + mPacket.length
+                + ']';
+    }
 }
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index b6cd760..41be732 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -1536,9 +1536,12 @@
      */
     public @NonNull NetworkCapabilities setNetworkSpecifier(
             @NonNull NetworkSpecifier networkSpecifier) {
-        if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) {
-            throw new IllegalStateException("Must have a single transport specified to use " +
-                    "setNetworkSpecifier");
+        if (networkSpecifier != null
+                // Transport can be test, or test + a single other transport
+                && mTransportTypes != (1L << TRANSPORT_TEST)
+                && Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1) {
+            throw new IllegalStateException("Must have a single non-test transport specified to "
+                    + "use setNetworkSpecifier");
         }
 
         mNetworkSpecifier = networkSpecifier;
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index b7a6076..4f9d845 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -423,6 +423,7 @@
          *
          * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
          */
+        @SuppressLint("NewApi") // TODO: b/193460475 remove once fixed
         @Deprecated
         public Builder setNetworkSpecifier(String networkSpecifier) {
             try {
@@ -439,6 +440,15 @@
                 } else if (mNetworkCapabilities.hasTransport(TRANSPORT_TEST)) {
                     return setNetworkSpecifier(new TestNetworkSpecifier(networkSpecifier));
                 } else {
+                    // TODO: b/193460475 remove comment once fixed
+                    // @SuppressLint("NewApi") is due to EthernetNetworkSpecifier being changed
+                    // from @SystemApi to public. EthernetNetworkSpecifier was introduced in Android
+                    // 12 as @SystemApi(client = MODULE_LIBRARIES) and made public in Android 13.
+                    // b/193460475 means in the above situation the tools will think
+                    // EthernetNetworkSpecifier didn't exist in Android 12, causing the NewApi lint
+                    // to fail. In this case, this is actually safe because this code was
+                    // modularized in Android 12, so it can't run on SDKs before Android 12 and is
+                    // therefore guaranteed to always have this class available to it.
                     return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));
                 }
             }
diff --git a/netd/Android.bp b/netd/Android.bp
index b98a859..5ac02d3 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -13,6 +13,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libnetd_updatable",
     version_script: "libnetd_updatable.map.txt",
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 2aaa4c3..85cfc09 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -116,27 +116,11 @@
     return (jint)res;
 }
 
-static FirewallType getFirewallType(ChildChain chain) {
-    switch (chain) {
-        case DOZABLE:
-            return ALLOWLIST;
-        case STANDBY:
-            return DENYLIST;
-        case POWERSAVE:
-            return ALLOWLIST;
-        case RESTRICTED:
-            return ALLOWLIST;
-        case NONE:
-        default:
-            return DENYLIST;
-    }
-}
-
 static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
                           jint firewallRule) {
     auto chain = static_cast<ChildChain>(childChain);
     auto rule = static_cast<FirewallRule>(firewallRule);
-    FirewallType fType = getFirewallType(chain);
+    FirewallType fType = mTc.getFirewallType(chain);
 
     int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
     if (res) {
@@ -202,24 +186,6 @@
     mTc.setPermissionForUids(permission, data);
 }
 
-static jint native_setCounterSet(JNIEnv* env, jobject clazz, jint setNum, jint uid) {
-    uid_t callingUid = getuid();
-    int res = mTc.setCounterSet(setNum, (uid_t)uid, callingUid);
-    if (res) {
-      ALOGE("%s failed, error code = %d", __func__, res);
-    }
-    return (jint)res;
-}
-
-static jint native_deleteTagData(JNIEnv* env, jobject clazz, jint tagNum, jint uid) {
-    uid_t callingUid = getuid();
-    int res = mTc.deleteTagData(tagNum, (uid_t)uid, callingUid);
-    if (res) {
-      ALOGE("%s failed, error code = %d", __func__, res);
-    }
-    return (jint)res;
-}
-
 /*
  * JNI registration.
  */
@@ -250,10 +216,6 @@
     (void*)native_swapActiveStatsMap},
     {"native_setPermissionForUids", "(I[I)V",
     (void*)native_setPermissionForUids},
-    {"native_setCounterSet", "(II)I",
-    (void*)native_setCounterSet},
-    {"native_deleteTagData", "(II)I",
-    (void*)native_deleteTagData},
 };
 // clang-format on
 
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 5981906..1cbfd94 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -54,7 +54,6 @@
 using base::StringPrintf;
 using base::unique_fd;
 using bpf::BpfMap;
-using bpf::OVERFLOW_COUNTERSET;
 using bpf::synchronizeKernelRCU;
 using netdutils::DumpWriter;
 using netdutils::getIfaceList;
@@ -239,99 +238,6 @@
     return netdutils::status::ok;
 }
 
-int TrafficController::setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) {
-    if (counterSetNum < 0 || counterSetNum >= OVERFLOW_COUNTERSET) return -EINVAL;
-
-    std::lock_guard guard(mMutex);
-    if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
-
-    // The default counter set for all uid is 0, so deleting the current counterset for that uid
-    // will automatically set it to 0.
-    if (counterSetNum == 0) {
-        Status res = mUidCounterSetMap.deleteValue(uid);
-        if (isOk(res) || (!isOk(res) && res.code() == ENOENT)) {
-            return 0;
-        } else {
-            ALOGE("Failed to delete the counterSet: %s\n", strerror(res.code()));
-            return -res.code();
-        }
-    }
-    uint8_t tmpCounterSetNum = (uint8_t)counterSetNum;
-    Status res = mUidCounterSetMap.writeValue(uid, tmpCounterSetNum, BPF_ANY);
-    if (!isOk(res)) {
-        ALOGE("Failed to set the counterSet: %s, fd: %d", strerror(res.code()),
-              mUidCounterSetMap.getMap().get());
-        return -res.code();
-    }
-    return 0;
-}
-
-// This method only get called by system_server when an app get uinstalled, it
-// is called inside removeUidsLocked() while holding mStatsLock. So it is safe
-// to iterate and modify the stats maps.
-int TrafficController::deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) {
-    std::lock_guard guard(mMutex);
-    if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
-
-    // First we go through the cookieTagMap to delete the target uid tag combination. Or delete all
-    // the tags related to the uid if the tag is 0.
-    const auto deleteMatchedCookieEntries = [uid, tag](const uint64_t& key,
-                                                       const UidTagValue& value,
-                                                       BpfMap<uint64_t, UidTagValue>& map) {
-        if (value.uid == uid && (value.tag == tag || tag == 0)) {
-            auto res = map.deleteValue(key);
-            if (res.ok() || (res.error().code() == ENOENT)) {
-                return base::Result<void>();
-            }
-            ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
-                  strerror(res.error().code()));
-        }
-        // Move forward to next cookie in the map.
-        return base::Result<void>();
-    };
-    mCookieTagMap.iterateWithValue(deleteMatchedCookieEntries);
-    // Now we go through the Tag stats map and delete the data entry with correct uid and tag
-    // combination. Or all tag stats under that uid if the target tag is 0.
-    const auto deleteMatchedUidTagEntries = [uid, tag](const StatsKey& key,
-                                                       BpfMap<StatsKey, StatsValue>& map) {
-        if (key.uid == uid && (key.tag == tag || tag == 0)) {
-            auto res = map.deleteValue(key);
-            if (res.ok() || (res.error().code() == ENOENT)) {
-                //Entry is deleted, use the current key to get a new nextKey;
-                return base::Result<void>();
-            }
-            ALOGE("Failed to delete data(uid=%u, tag=%u): %s\n", key.uid, key.tag,
-                  strerror(res.error().code()));
-        }
-        return base::Result<void>();
-    };
-    mStatsMapB.iterate(deleteMatchedUidTagEntries);
-    mStatsMapA.iterate(deleteMatchedUidTagEntries);
-    // If the tag is not zero, we already deleted all the data entry required. If tag is 0, we also
-    // need to delete the stats stored in uidStatsMap and counterSet map.
-    if (tag != 0) return 0;
-
-    auto res = mUidCounterSetMap.deleteValue(uid);
-    if (!res.ok() && res.error().code() != ENOENT) {
-        ALOGE("Failed to delete counterSet data(uid=%u, tag=%u): %s\n", uid, tag,
-              strerror(res.error().code()));
-    }
-
-    auto deleteAppUidStatsEntry = [uid](const uint32_t& key,
-                                        BpfMap<uint32_t, StatsValue>& map) -> base::Result<void> {
-        if (key == uid) {
-            auto res = map.deleteValue(key);
-            if (res.ok() || (res.error().code() == ENOENT)) {
-                return {};
-            }
-            ALOGE("Failed to delete data(uid=%u): %s", key, strerror(res.error().code()));
-        }
-        return {};
-    };
-    mAppUidStatsMap.iterate(deleteAppUidStatsEntry);
-    return 0;
-}
-
 int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
     IfaceValue iface;
     if (ifaceIndex == 0) {
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index d0eca34..9529cae 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -55,7 +55,6 @@
 constexpr uid_t TEST_UID3 = 98765;
 constexpr uint32_t TEST_TAG = 42;
 constexpr uint32_t TEST_COUNTERSET = 1;
-constexpr uint32_t DEFAULT_COUNTERSET = 0;
 
 #define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
 
@@ -64,7 +63,6 @@
     TrafficControllerTest() {}
     TrafficController mTc;
     BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
-    BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
     BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
     BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
     BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
@@ -79,10 +77,6 @@
                                           TEST_MAP_SIZE, 0));
         ASSERT_VALID(mFakeCookieTagMap);
 
-        mFakeUidCounterSetMap.reset(
-            createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
-        ASSERT_VALID(mFakeUidCounterSetMap);
-
         mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(StatsValue),
                                             TEST_MAP_SIZE, 0));
         ASSERT_VALID(mFakeAppUidStatsMap);
@@ -104,8 +98,6 @@
 
         mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
         ASSERT_VALID(mTc.mCookieTagMap);
-        mTc.mUidCounterSetMap.reset(dupFd(mFakeUidCounterSetMap.getMap()));
-        ASSERT_VALID(mTc.mUidCounterSetMap);
         mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
         ASSERT_VALID(mTc.mAppUidStatsMap);
         mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
@@ -132,8 +124,6 @@
         EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
         *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
         StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
-        uint8_t counterSet = TEST_COUNTERSET;
-        EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
         EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
         key->tag = 0;
         EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
@@ -259,9 +249,6 @@
         EXPECT_RESULT_OK(cookieMapResult);
         EXPECT_EQ(uid, cookieMapResult.value().uid);
         EXPECT_EQ(tag, cookieMapResult.value().tag);
-        Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-        EXPECT_RESULT_OK(counterSetResult);
-        EXPECT_EQ(TEST_COUNTERSET, counterSetResult.value());
         Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
         EXPECT_RESULT_OK(statsMapResult);
         EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
@@ -289,157 +276,6 @@
 
 };
 
-TEST_F(TrafficControllerTest, TestSetCounterSet) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    ASSERT_EQ(0, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, callingUid));
-    uid_t uid = TEST_UID;
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_EQ(0, mTc.setCounterSet(DEFAULT_COUNTERSET, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    expectMapEmpty(mFakeUidCounterSetMap);
-}
-
-TEST_F(TrafficControllerTest, TestSetCounterSetWithoutPermission) {
-    ASSERT_EQ(-EPERM, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, TEST_UID2));
-    uid_t uid = TEST_UID;
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    expectMapEmpty(mFakeUidCounterSetMap);
-}
-
-TEST_F(TrafficControllerTest, TestSetInvalidCounterSet) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    ASSERT_GT(0, mTc.setCounterSet(OVERFLOW_COUNTERSET, TEST_UID, callingUid));
-    uid_t uid = TEST_UID;
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    expectMapEmpty(mFakeUidCounterSetMap);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteTagDataWithoutPermission) {
-    uint64_t cookie = 1;
-    uid_t uid = TEST_UID;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey;
-    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(-EPERM, mTc.deleteTagData(0, TEST_UID, TEST_UID2));
-
-    expectFakeStatsUnchanged(cookie, tag, uid, tagStatsMapKey);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteTagData) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie = 1;
-    uid_t uid = TEST_UID;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey;
-    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie).ok());
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
-    tagStatsMapKey.tag = 0;
-    Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
-    ASSERT_RESULT_OK(statsMapResult);
-    ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
-    auto appStatsResult = mFakeAppUidStatsMap.readValue(TEST_UID);
-    ASSERT_RESULT_OK(appStatsResult);
-    ASSERT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteAllUidData) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie = 1;
-    uid_t uid = TEST_UID;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey;
-    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(0, mTc.deleteTagData(0, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie).ok());
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
-    tagStatsMapKey.tag = 0;
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
-    ASSERT_FALSE(mFakeAppUidStatsMap.readValue(TEST_UID).ok());
-}
-
-TEST_F(TrafficControllerTest, TestDeleteDataWithTwoTags) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie1 = 1;
-    uint64_t cookie2 = 2;
-    uid_t uid = TEST_UID;
-    uint32_t tag1 = TEST_TAG;
-    uint32_t tag2 = TEST_TAG + 1;
-    StatsKey tagStatsMapKey1;
-    StatsKey tagStatsMapKey2;
-    populateFakeStats(cookie1, uid, tag1, &tagStatsMapKey1);
-    populateFakeStats(cookie2, uid, tag2, &tagStatsMapKey2);
-    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie1).ok());
-    Result<UidTagValue> cookieMapResult = mFakeCookieTagMap.readValue(cookie2);
-    ASSERT_RESULT_OK(cookieMapResult);
-    ASSERT_EQ(TEST_UID, cookieMapResult.value().uid);
-    ASSERT_EQ(TEST_TAG + 1, cookieMapResult.value().tag);
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey1).ok());
-    Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey2);
-    ASSERT_RESULT_OK(statsMapResult);
-    ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteDataWithTwoUids) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie1 = 1;
-    uint64_t cookie2 = 2;
-    uid_t uid1 = TEST_UID;
-    uid_t uid2 = TEST_UID + 1;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey1;
-    StatsKey tagStatsMapKey2;
-    populateFakeStats(cookie1, uid1, tag, &tagStatsMapKey1);
-    populateFakeStats(cookie2, uid2, tag, &tagStatsMapKey2);
-
-    // Delete the stats of one of the uid. Check if it is properly collected by
-    // removedStats.
-    ASSERT_EQ(0, mTc.deleteTagData(0, uid2, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie2).ok());
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid1);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid2).ok());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey2).ok());
-    tagStatsMapKey2.tag = 0;
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey2).ok());
-    ASSERT_FALSE(mFakeAppUidStatsMap.readValue(uid2).ok());
-    tagStatsMapKey1.tag = 0;
-    Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey1);
-    ASSERT_RESULT_OK(statsMapResult);
-    ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
-    auto appStatsResult = mFakeAppUidStatsMap.readValue(uid1);
-    ASSERT_RESULT_OK(appStatsResult);
-    ASSERT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
-
-    // Delete the stats of the other uid.
-    ASSERT_EQ(0, mTc.deleteTagData(0, uid1, callingUid));
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey1).ok());
-    ASSERT_FALSE(mFakeAppUidStatsMap.readValue(uid1).ok());
-}
-
 TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
     uint32_t uid = TEST_UID;
     ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, DENYLIST)));
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index e741dd6..6fe117f 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -40,18 +40,6 @@
      */
     netdutils::Status start();
 
-    int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
-
-    /*
-     * When deleting a tag data, the qtaguid module will grab the spinlock of each
-     * related rb_tree one by one and delete the tag information, counterSet
-     * information, iface stats information and uid stats information one by one.
-     * The new eBPF implementation is done similiarly by removing the entry on
-     * each map one by one. And deleting processes are also protected by the
-     * spinlock of the map. So no additional lock is required.
-     */
-    int deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
-
     /*
      * Swap the stats map config from current active stats map to the idle one.
      */
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 7a3bab3..ddee275 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -273,32 +273,6 @@
         native_setPermissionForUids(permissions, uids);
     }
 
-    /**
-     * Set counter set for uid
-     *
-     * @param counterSet either SET_DEFAULT or SET_FOREGROUND
-     * @param uid        uid to foreground/background
-     * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *                                  cause of the failure.
-     */
-    public void setCounterSet(final int counterSet, final int uid) {
-        final int err = native_setCounterSet(counterSet, uid);
-        maybeThrow(err, "setCounterSet failed");
-    }
-
-    /**
-     * Reset Uid stats
-     *
-     * @param tag default 0
-     * @param uid given uid to be clear
-     * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *                                  cause of the failure.
-     */
-    public void deleteTagData(final int tag, final int uid) {
-        final int err = native_deleteTagData(tag, uid);
-        maybeThrow(err, "deleteTagData failed");
-    }
-
     private static native void native_init();
     private native int native_addNaughtyApp(int uid);
     private native int native_removeNaughtyApp(int uid);
@@ -311,6 +285,4 @@
     private native int native_removeUidInterfaceRules(int[] uids);
     private native int native_swapActiveStatsMap();
     private native void native_setPermissionForUids(int permissions, int[] uids);
-    private native int native_setCounterSet(int counterSet, int uid);
-    private native int native_deleteTagData(int tag, int uid);
 }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 7bb4529..6024a2a 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -2164,6 +2164,19 @@
         }
     }
 
+    @Override
+    @Nullable
+    public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
+            @NonNull String packageName, @Nullable String callingAttributionTag) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(lp);
+        enforceNetworkStackOrSettingsPermission();
+        if (!checkAccessPermission(-1 /* pid */, uid)) {
+            return null;
+        }
+        return linkPropertiesRestrictedForCallerPermissions(lp, -1 /* callerPid */, uid);
+    }
+
     private NetworkCapabilities getNetworkCapabilitiesInternal(Network network) {
         return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
     }
@@ -2187,13 +2200,34 @@
                 getCallingPid(), mDeps.getCallingUid(), callingPackageName, callingAttributionTag);
     }
 
+    @Override
+    public NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull NetworkCapabilities nc,
+            int uid, @NonNull String packageName, @Nullable String callingAttributionTag) {
+        Objects.requireNonNull(nc);
+        Objects.requireNonNull(packageName);
+        enforceNetworkStackOrSettingsPermission();
+        if (!checkAccessPermission(-1 /* pid */, uid)) {
+            return null;
+        }
+        return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+                networkCapabilitiesRestrictedForCallerPermissions(nc, -1 /* callerPid */, uid),
+                true /* includeLocationSensitiveInfo */, -1 /* callingPid */, uid, packageName,
+                callingAttributionTag);
+    }
+
     @VisibleForTesting
     NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
             NetworkCapabilities nc, int callerPid, int callerUid) {
+        // Note : here it would be nice to check ACCESS_NETWORK_STATE and return null, but
+        // this would be expensive (one more permission check every time any NC callback is
+        // sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if
+        // it happens for some reason (e.g. the package is uninstalled while CS is trying to
+        // send the callback) it would crash the system server with NPE.
         final NetworkCapabilities newNc = new NetworkCapabilities(nc);
         if (!checkSettingsPermission(callerPid, callerUid)) {
             newNc.setUids(null);
             newNc.setSSID(null);
+            // TODO: Processes holding NETWORK_FACTORY should be able to see the underlying networks
             newNc.setUnderlyingNetworks(null);
         }
         if (newNc.getNetworkSpecifier() != null) {
@@ -2211,7 +2245,7 @@
 
     /**
      * Wrapper used to cache the permission check results performed for the corresponding
-     * app. This avoid performing multiple permission checks for different fields in
+     * app. This avoids performing multiple permission checks for different fields in
      * NetworkCapabilities.
      * Note: This wrapper does not support any sort of invalidation and thus must not be
      * persistent or long-lived. It may only be used for the time necessary to
@@ -2339,6 +2373,8 @@
                 includeLocationSensitiveInfo);
         final NetworkCapabilities newNc = new NetworkCapabilities(nc, redactions);
         // Reset owner uid if not destined for the owner app.
+        // TODO : calling UID is redacted because apps should generally not know what UID is
+        // bringing up the VPN, but this should not apply to some very privileged apps like settings
         if (callingUid != nc.getOwnerUid()) {
             newNc.setOwnerUid(INVALID_UID);
             return newNc;
@@ -2364,9 +2400,15 @@
         return newNc;
     }
 
+    @NonNull
     private LinkProperties linkPropertiesRestrictedForCallerPermissions(
             LinkProperties lp, int callerPid, int callerUid) {
         if (lp == null) return new LinkProperties();
+        // Note : here it would be nice to check ACCESS_NETWORK_STATE and return null, but
+        // this would be expensive (one more permission check every time any LP callback is
+        // sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if
+        // it happens for some reason (e.g. the package is uninstalled while CS is trying to
+        // send the callback) it would crash the system server with NPE.
 
         // Only do a permission check if sanitization is needed, to avoid unnecessary binder calls.
         final boolean needsSanitization =
@@ -2737,6 +2779,11 @@
                 "ConnectivityService");
     }
 
+    private boolean checkAccessPermission(int pid, int uid) {
+        return mContext.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, pid, uid)
+                == PERMISSION_GRANTED;
+    }
+
     /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
      * change the state of network, as the condition differs for pre-M, M+, and
diff --git a/tests/TEST_MAPPING b/tests/TEST_MAPPING
deleted file mode 100644
index 502f885..0000000
--- a/tests/TEST_MAPPING
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "FrameworksNetIntegrationTests"
-    }
-  ],
-  "postsubmit": [
-    {
-      "name": "FrameworksNetDeflakeTest"
-    }
-  ],
-  "auto-postsubmit": [
-    // Test tag for automotive targets. These are only running in postsubmit so as to harden the
-    // automotive targets to avoid introducing additional test flake and build time. The plan for
-    // presubmit testing for auto is to augment the existing tests to cover auto use cases as well.
-    // Additionally, this tag is used in targeted test suites to limit resource usage on the test
-    // infra during the hardening phase.
-    // TODO: this tag to be removed once the above is no longer an issue.
-    {
-      "name": "FrameworksNetTests"
-    },
-    {
-      "name": "FrameworksNetIntegrationTests"
-    },
-    {
-      "name": "FrameworksNetDeflakeTest"
-    }
-  ],
-  "imports": [
-    {
-      "path": "packages/modules/Connectivity"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
index 8d8958d..d14d127 100644
--- a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
+++ b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
@@ -67,6 +67,7 @@
 import androidx.test.InstrumentationRegistry
 import androidx.test.filters.SmallTest
 import com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import junit.framework.Assert.assertEquals
@@ -295,6 +296,7 @@
                 testIntValues = intArrayOf(0))
     }
 
+    @ConnectivityModuleTest // get/setIngressRateLimitInBytesPerSecond was added via module update
     @Test
     fun testInternetNetworkRateLimitInBytesPerSecond() {
         val defaultRate = getIngressRateLimitInBytesPerSecond(context)
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 742044b..b6926a8 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -49,6 +49,7 @@
 import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
 import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_USB;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -727,25 +728,38 @@
     @Test
     public void testSetNetworkSpecifierOnMultiTransportNc() {
         // Sequence 1: Transport + Transport + NetworkSpecifier
-        NetworkCapabilities nc1 = new NetworkCapabilities();
+        NetworkCapabilities.Builder nc1 = new NetworkCapabilities.Builder();
         nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI);
-        try {
-            nc1.setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier("eth0"));
-            fail("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!");
-        } catch (IllegalStateException expected) {
-            // empty
-        }
+        final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
+        assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+                IllegalStateException.class,
+                () -> nc1.build().setNetworkSpecifier(specifier));
+        assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+                IllegalStateException.class,
+                () -> nc1.setNetworkSpecifier(specifier));
 
         // Sequence 2: Transport + NetworkSpecifier + Transport
-        NetworkCapabilities nc2 = new NetworkCapabilities();
-        nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier(
-                CompatUtil.makeEthernetNetworkSpecifier("testtap3"));
-        try {
-            nc2.addTransportType(TRANSPORT_WIFI);
-            fail("Cannot set a second TransportType of a network which has a NetworkSpecifier!");
-        } catch (IllegalStateException expected) {
-            // empty
-        }
+        NetworkCapabilities.Builder nc2 = new NetworkCapabilities.Builder();
+        nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier(specifier);
+
+        assertThrows("Cannot set a second TransportType of a network which has a NetworkSpecifier!",
+                IllegalStateException.class,
+                () -> nc2.build().addTransportType(TRANSPORT_WIFI));
+        assertThrows("Cannot set a second TransportType of a network which has a NetworkSpecifier!",
+                IllegalStateException.class,
+                () -> nc2.addTransportType(TRANSPORT_WIFI));
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) // New behavior in updatable NetworkCapabilities (S+)
+    public void testSetNetworkSpecifierOnTestMultiTransportNc() {
+        final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
+        NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .addTransportType(TRANSPORT_ETHERNET)
+                .setNetworkSpecifier(specifier)
+                .build();
+        // Adding a specifier did not crash with 2 transports if one is TEST
+        assertEquals(specifier, nc.getNetworkSpecifier());
     }
 
     @Test
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index d761c27..d605799 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -21,6 +21,8 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 53e4ab7..ea64252 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -16,9 +16,15 @@
 
 package android.net.cts;
 
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
 import static android.content.pm.PackageManager.FEATURE_ETHERNET;
@@ -54,7 +60,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
 import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
 import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
@@ -64,6 +72,7 @@
 import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL;
 import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.os.Process.INVALID_UID;
 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
@@ -76,6 +85,7 @@
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
 import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -103,6 +113,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.net.CaptivePortalData;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivitySettingsManager;
@@ -132,6 +143,7 @@
 import android.net.cts.util.CtsNetUtils;
 import android.net.cts.util.CtsTetheringUtils;
 import android.net.util.KeepaliveUtils;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Build;
@@ -160,6 +172,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.CollectionUtils;
 import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
@@ -554,6 +567,223 @@
         }
     }
 
+    private boolean checkPermission(String perm, int uid) {
+        return mContext.checkPermission(perm, -1 /* pid */, uid) == PERMISSION_GRANTED;
+    }
+
+    private String findPackageByPermissions(@NonNull List<String> requiredPermissions,
+                @NonNull List<String> forbiddenPermissions) throws Exception {
+        final List<PackageInfo> packageInfos =
+                mPackageManager.getInstalledPackages(GET_PERMISSIONS);
+        for (PackageInfo packageInfo : packageInfos) {
+            final int uid = mPackageManager.getPackageUid(packageInfo.packageName, 0 /* flags */);
+            if (!CollectionUtils.all(requiredPermissions, perm -> checkPermission(perm, uid))) {
+                continue;
+            }
+            if (CollectionUtils.any(forbiddenPermissions, perm -> checkPermission(perm, uid))) {
+                continue;
+            }
+
+            return packageInfo.packageName;
+        }
+        return null;
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testRedactLinkPropertiesForPackage() throws Exception {
+        final String groundedPkg = findPackageByPermissions(
+                List.of(), /* requiredPermissions */
+                List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */);
+        assertNotNull("Couldn't find any package without ACCESS_NETWORK_STATE", groundedPkg);
+        final int groundedUid = mPackageManager.getPackageUid(groundedPkg, 0 /* flags */);
+
+        final String normalPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE) /* requiredPermissions */,
+                List.of(NETWORK_SETTINGS, NETWORK_STACK,
+                        PERMISSION_MAINLINE_NETWORK_STACK) /* forbiddenPermissions */);
+        assertNotNull("Couldn't find any package with ACCESS_NETWORK_STATE but"
+                + " without NETWORK_SETTINGS", normalPkg);
+        final int normalUid = mPackageManager.getPackageUid(normalPkg, 0 /* flags */);
+
+        // There are some privileged packages on the system, like the phone process, the network
+        // stack and the system server.
+        final String privilegedPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE, NETWORK_SETTINGS), /* requiredPermissions */
+                List.of() /* forbiddenPermissions */);
+        assertNotNull("Couldn't find a package with sufficient permissions", privilegedPkg);
+        final int privilegedUid = mPackageManager.getPackageUid(privilegedPkg, 0);
+
+        // Set parcelSensitiveFields to true to preserve CaptivePortalApiUrl & CaptivePortalData
+        // when parceling.
+        final LinkProperties lp = new LinkProperties(new LinkProperties(),
+                true /* parcelSensitiveFields */);
+        final Uri capportUrl = Uri.parse("https://capport.example.com/api");
+        final CaptivePortalData capportData = new CaptivePortalData.Builder().build();
+        final int mtu = 12345;
+        lp.setMtu(mtu);
+        lp.setCaptivePortalApiUrl(capportUrl);
+        lp.setCaptivePortalData(capportData);
+
+        // No matter what the given uid is, a SecurityException will be thrown if the caller
+        // doesn't hold the NETWORK_SETTINGS permission.
+        assertThrows(SecurityException.class,
+                () -> mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
+        assertThrows(SecurityException.class,
+                () -> mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg));
+        assertThrows(SecurityException.class,
+                () -> mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg));
+
+        runAsShell(NETWORK_SETTINGS, () -> {
+            // No matter what the given uid is, if the given LinkProperties is null, then
+            // NullPointerException will be thrown.
+            assertThrows(NullPointerException.class,
+                    () -> mCm.redactLinkPropertiesForPackage(null, groundedUid, groundedPkg));
+            assertThrows(NullPointerException.class,
+                    () -> mCm.redactLinkPropertiesForPackage(null, normalUid, normalPkg));
+            assertThrows(NullPointerException.class,
+                    () -> mCm.redactLinkPropertiesForPackage(null, privilegedUid, privilegedPkg));
+
+            // Make sure null is returned for a UID without ACCESS_NETWORK_STATE.
+            assertNull(mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
+
+            // CaptivePortalApiUrl & CaptivePortalData will be set to null if given uid doesn't hold
+            // the NETWORK_SETTINGS permission.
+            assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+                    .getCaptivePortalApiUrl());
+            assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+                    .getCaptivePortalData());
+            // MTU is not sensitive and is not redacted.
+            assertEquals(mtu, mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+                    .getMtu());
+
+            // CaptivePortalApiUrl & CaptivePortalData will be preserved if the given uid holds the
+            // NETWORK_SETTINGS permission.
+            assertEquals(capportUrl,
+                    mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
+                            .getCaptivePortalApiUrl());
+            assertEquals(capportData,
+                    mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
+                            .getCaptivePortalData());
+        });
+    }
+
+    private NetworkCapabilities redactNc(@NonNull final NetworkCapabilities nc, int uid,
+            @NonNull String packageName) {
+        return mCm.redactNetworkCapabilitiesForPackage(nc, uid, packageName);
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testRedactNetworkCapabilitiesForPackage() throws Exception {
+        final String groundedPkg = findPackageByPermissions(
+                List.of(), /* requiredPermissions */
+                List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */);
+        assertNotNull("Couldn't find any package without ACCESS_NETWORK_STATE", groundedPkg);
+        final int groundedUid = mPackageManager.getPackageUid(groundedPkg, 0 /* flags */);
+
+        // A package which doesn't have any of the permissions below, but has NETWORK_STATE.
+        // There should be a number of packages like this on the device; AOSP has many,
+        // including contacts, webview, the keyboard, pacprocessor, messaging.
+        final String normalPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE) /* requiredPermissions */,
+                List.of(NETWORK_SETTINGS, NETWORK_FACTORY, NETWORK_SETUP_WIZARD,
+                        NETWORK_STACK, PERMISSION_MAINLINE_NETWORK_STACK,
+                        ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) /* forbiddenPermissions */);
+        assertNotNull("Can't find a package with ACCESS_NETWORK_STATE but without any of"
+                + " the forbidden permissions", normalPkg);
+        final int normalUid = mPackageManager.getPackageUid(normalPkg, 0 /* flags */);
+
+        // There are some privileged packages on the system, like the phone process, the network
+        // stack and the system server.
+        final String privilegedPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE, NETWORK_SETTINGS, NETWORK_FACTORY,
+                        ACCESS_FINE_LOCATION), /* requiredPermissions */
+                List.of() /* forbiddenPermissions */);
+        assertNotNull("Couldn't find a package with sufficient permissions", privilegedPkg);
+        final int privilegedUid = mPackageManager.getPackageUid(privilegedPkg, 0);
+
+        final Set<Range<Integer>> uids = new ArraySet<>();
+        uids.add(new Range<>(10000, 10100));
+        uids.add(new Range<>(10200, 10300));
+        final String ssid = "My-WiFi";
+        // This test will set underlying networks in the capabilities to redact to see if they
+        // are appropriately redacted, so fetch the default network to put in there as an example.
+        final Network defaultNetwork = mCm.getActiveNetwork();
+        assertNotNull("CTS requires a working Internet connection", defaultNetwork);
+        final int subId1 = 1;
+        final int subId2 = 2;
+        final int[] administratorUids = {normalUid};
+        final String bssid = "location sensitive";
+        final int rssi = 43; // not location sensitive
+        final WifiInfo wifiInfo = new WifiInfo.Builder()
+                .setBssid(bssid)
+                .setRssi(rssi)
+                .build();
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .setUids(uids)
+                .setSsid(ssid)
+                .setUnderlyingNetworks(List.of(defaultNetwork))
+                .setSubscriptionIds(Set.of(subId1, subId2))
+                .setAdministratorUids(administratorUids)
+                .setOwnerUid(normalUid)
+                .setTransportInfo(wifiInfo)
+                .build();
+
+        // No matter what the given uid is, a SecurityException will be thrown if the caller
+        // doesn't hold the NETWORK_SETTINGS permission.
+        assertThrows(SecurityException.class, () -> redactNc(nc, groundedUid, groundedPkg));
+        assertThrows(SecurityException.class, () -> redactNc(nc, normalUid, normalPkg));
+        assertThrows(SecurityException.class, () -> redactNc(nc, privilegedUid, privilegedPkg));
+
+        runAsShell(NETWORK_SETTINGS, () -> {
+            // Make sure that the NC is null if the package doesn't hold ACCESS_NETWORK_STATE.
+            assertNull(redactNc(nc, groundedUid, groundedPkg));
+
+            // Uids, ssid, underlying networks & subscriptionIds will be redacted if the given uid
+            // doesn't hold the associated permissions. The wifi transport info is also suitably
+            // redacted.
+            final NetworkCapabilities redactedNormal = redactNc(nc, normalUid, normalPkg);
+            assertNull(redactedNormal.getUids());
+            assertNull(redactedNormal.getSsid());
+            assertNull(redactedNormal.getUnderlyingNetworks());
+            assertEquals(0, redactedNormal.getSubscriptionIds().size());
+            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS,
+                    ((WifiInfo) redactedNormal.getTransportInfo()).getBSSID());
+            assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi());
+
+            // Uids, ssid, underlying networks & subscriptionIds will be preserved if the given uid
+            // holds the associated permissions.
+            final NetworkCapabilities redactedPrivileged =
+                    redactNc(nc, privilegedUid, privilegedPkg);
+            assertEquals(uids, redactedPrivileged.getUids());
+            assertEquals(ssid, redactedPrivileged.getSsid());
+            assertEquals(List.of(defaultNetwork), redactedPrivileged.getUnderlyingNetworks());
+            assertEquals(Set.of(subId1, subId2), redactedPrivileged.getSubscriptionIds());
+            assertEquals(bssid, ((WifiInfo) redactedPrivileged.getTransportInfo()).getBSSID());
+            assertEquals(rssi, ((WifiInfo) redactedPrivileged.getTransportInfo()).getRssi());
+
+            // The owner uid is only preserved when the network is a VPN and the uid is the
+            // same as the owner uid.
+            nc.addTransportType(TRANSPORT_VPN);
+            assertEquals(normalUid, redactNc(nc, normalUid, normalPkg).getOwnerUid());
+            assertEquals(INVALID_UID, redactNc(nc, privilegedUid, privilegedPkg).getOwnerUid());
+            nc.removeTransportType(TRANSPORT_VPN);
+
+            // If the given uid doesn't hold location permissions, the owner uid will be set to
+            // INVALID_UID even when sent to that UID (this avoids a wifi suggestor knowing where
+            // the device is by virtue of the device connecting to its own network).
+            assertEquals(INVALID_UID, redactNc(nc, normalUid, normalPkg).getOwnerUid());
+
+            // If the given uid holds location permissions, the owner uid is preserved. This works
+            // because the shell holds ACCESS_FINE_LOCATION.
+            final int[] administratorUids2 = { privilegedUid };
+            nc.setAdministratorUids(administratorUids2);
+            nc.setOwnerUid(privilegedUid);
+            assertEquals(privilegedUid, redactNc(nc, privilegedUid, privilegedPkg).getOwnerUid());
+        });
+    }
+
     /**
      * Tests that connections can be opened on WiFi and cellphone networks,
      * and that they are made from different IP addresses.
diff --git a/tests/cts/net/src/android/net/cts/EthernetNetworkSpecifierTest.java b/tests/cts/net/src/android/net/cts/EthernetNetworkSpecifierTest.java
new file mode 100644
index 0000000..5bde8c8
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/EthernetNetworkSpecifierTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.net.EthernetNetworkSpecifier;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+public class EthernetNetworkSpecifierTest {
+
+    @Test
+    public void testConstructor() {
+        final String iface = "testIface";
+        final EthernetNetworkSpecifier ns = new EthernetNetworkSpecifier(iface);
+        assertEquals(iface, ns.getInterfaceName());
+    }
+
+    @Test
+    public void testConstructorWithNullValue() {
+        assertThrows("Should not be able to call constructor with null value.",
+                IllegalArgumentException.class,
+                () -> new EthernetNetworkSpecifier(null));
+    }
+
+    @Test
+    public void testConstructorWithEmptyValue() {
+        assertThrows("Should not be able to call constructor with empty value.",
+                IllegalArgumentException.class,
+                () -> new EthernetNetworkSpecifier(""));
+    }
+
+    @Test
+    public void testEquals() {
+        final String iface = "testIface";
+        final EthernetNetworkSpecifier nsOne = new EthernetNetworkSpecifier(iface);
+        final EthernetNetworkSpecifier nsTwo = new EthernetNetworkSpecifier(iface);
+        assertEquals(nsOne, nsTwo);
+    }
+
+    @Test
+    public void testNotEquals() {
+        final String iface = "testIface";
+        final String ifaceTwo = "testIfaceTwo";
+        final EthernetNetworkSpecifier nsOne = new EthernetNetworkSpecifier(iface);
+        final EthernetNetworkSpecifier nsTwo = new EthernetNetworkSpecifier(ifaceTwo);
+        assertNotEquals(nsOne, nsTwo);
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
index d221694..23744eb 100644
--- a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
@@ -29,6 +29,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 
 import libcore.net.InetAddressUtils;
@@ -106,7 +107,7 @@
         assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
     }
 
-    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @ConnectivityModuleTest @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
     @Test
     public void testBuilder() {
         final IpConfiguration c = new IpConfiguration.Builder()
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 344482b..bf97339 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -77,6 +77,7 @@
 import com.android.modules.utils.build.SdkLevel
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.testutils.CompatUtil
+import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
@@ -465,6 +466,7 @@
                 .addTransportType(TRANSPORT_TEST)
                 .setAccessUids(uids.toSet()).build()
 
+    @ConnectivityModuleTest // Functionality was added post-S via connectivity module update
     @Test
     fun testRejectedUpdates() {
         val callback = TestableNetworkCallback()
diff --git a/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java b/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
index 9b2756c..e2d3346 100644
--- a/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 
 import org.junit.Rule;
@@ -256,7 +257,7 @@
         assertEquals(DNS1, s.getDnsServers().get(0));
     }
 
-    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @ConnectivityModuleTest @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
     @Test
     public void testIllegalBuilders() {
         assertThrows("Can't set IP Address to IPv6!", IllegalArgumentException.class, () -> {
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index 2c44010..74fee3d 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -27,6 +27,9 @@
         "connectivity-mainline-presubmit-cc-defaults",
     ],
     require_root: true,
+    header_libs: [
+        "bpf_headers",
+    ],
     static_libs: [
         "libbase",
         "libmodules-utils-build",
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 142e013..2bba282 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -23,6 +23,7 @@
 #include <android/api-level.h>
 #include <android-base/properties.h>
 #include <android-modules-utils/sdk_level.h>
+#include <bpf/BpfUtils.h>
 
 #include <gtest/gtest.h>
 
@@ -151,6 +152,8 @@
 }
 
 TEST_F(BpfExistenceTest, TestPrograms) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
     // Pre-flight check to ensure test has been updated.
     uint64_t buildVersionSdk = android_get_device_api_level();
     ASSERT_NE(0, buildVersionSdk) << "Unable to determine device SDK version";
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index c3d3bf7..8559c20 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -264,8 +266,8 @@
     }
 
 
-    // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP
-    @DevSdkIgnoreRule.IgnoreUpTo(32)
+    // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
     @Test
     public void testBuildExcludeLocalRoutesSet() throws Exception {
         final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 76c0c38..34bcf3f 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -198,7 +198,10 @@
     @Mock
     private LocationPermissionChecker mLocationPermissionChecker;
     private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
-    private @Mock NetworkStatsService.TagStatsDeleter mTagStatsDeleter;
+    private @Mock IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
+    private @Mock IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
+    private @Mock IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
+    private @Mock IBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
@@ -361,8 +364,23 @@
             }
 
             @Override
-            public NetworkStatsService.TagStatsDeleter getTagStatsDeleter() {
-                return mTagStatsDeleter;
+            public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
+                return mCookieTagMap;
+            }
+
+            @Override
+            public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() {
+                return mStatsMapA;
+            }
+
+            @Override
+            public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() {
+                return mStatsMapB;
+            }
+
+            @Override
+            public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
+                return mAppUidStatsMap;
             }
         };
     }
@@ -702,10 +720,8 @@
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
-        verify(mTagStatsDeleter).deleteTagData(UID_BLUE);
         intent.putExtra(EXTRA_UID, UID_RED);
         mServiceContext.sendBroadcast(intent);
-        verify(mTagStatsDeleter).deleteTagData(UID_RED);
 
         // existing uid and total should remain unchanged; but removed UID
         // should be gone completely.