Merge "Fix DscpPolicyTest EBADF"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 780ba26..302c0b3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
       ]
     },
     {
+      "name": "netd_updatable_unit_test"
+    },
+    {
       "name": "TetheringTests"
     },
     {
@@ -40,6 +43,10 @@
       "name": "bpf_existence_test"
     },
     {
+      "name": "netd_updatable_unit_test",
+      "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+    },
+    {
       "name": "libclat_test"
     },
     {
@@ -63,6 +70,9 @@
       ]
     },
     {
+      "name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+    },
+    {
       "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     },
     {
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 53a14ad..ea3f8d6 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -52,9 +52,9 @@
         first: {
             jni_libs: [
                 "libservice-connectivity",
-                "libcom_android_connectivity_com_android_net_module_util_jni",
-                "libtraffic_controller_jni",
+                "libandroid_net_connectivity_com_android_net_module_util_jni",
             ],
+            native_shared_libs: ["libnetd_updatable"],
         },
         both: {
             jni_libs: ["libframework-connectivity-jni"],
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index cce2b71..6718402 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -42,6 +42,7 @@
         // TODO: remove it when NetworkStatsService is moved into the mainline module and no more
         // calls to JNI in libservices.core.
         "//frameworks/base/services/core/jni",
+        "//packages/modules/Connectivity/netd",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service/native/libs/libclat",
         "//packages/modules/Connectivity/Tethering",
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index abcfbeb..a6700e5 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -26,7 +26,9 @@
 // The above defaults can be used to disable framework-connectivity t
 // targets while minimizing merge conflicts in the build rules.
 
-
+// SDK library for connectivity bootclasspath classes that were part of the non-updatable API before
+// T, and were moved to the module in T. Other bootclasspath classes in connectivity should go to
+// framework-connectivity.
 java_sdk_library {
     name: "framework-connectivity-tiramisu",
     sdk_version: "module_current",
@@ -37,14 +39,40 @@
     ],
     srcs: [
         ":framework-connectivity-tiramisu-updatable-sources",
+        ":framework-nearby-java-sources",
+    ],
+    // Do not add static_libs to this library: put them in framework-connectivity instead.
+    // The jarjar rules are only so that references to jarjared utils in
+    // framework-connectivity-pre-jarjar match at runtime.
+    jarjar_rules: ":connectivity-jarjar-rules",
+    stub_only_libs: [
+        // Use prebuilt framework-connectivity stubs to avoid circular dependencies
+        "sdk_module-lib_current_framework-connectivity",
     ],
     libs: [
         "unsupportedappusage",
         "app-compat-annotations",
+        "sdk_module-lib_current_framework-connectivity",
+    ],
+    impl_only_libs: [
+        // The build system will use framework-bluetooth module_current stubs, because
+        // of sdk_version: "module_current" above.
+        "framework-bluetooth",
+        // Compile against the entire implementation of framework-connectivity,
+        // including hidden methods. This is safe because if framework-connectivity-t is
+        // on the bootclasspath (i.e., T), then framework-connectivity is also on the
+        // bootclasspath (because it shipped in S).
+        //
+        // This compiles against the pre-jarjar target so that this code can use
+        // non-jarjard names of widely-used packages such as com.android.net.module.util.
+        "framework-connectivity-pre-jarjar",
     ],
     permitted_packages: [
         "android.net",
         "android.net.nsd",
+        "android.nearby",
+        "com.android.connectivity",
+        "com.android.nearby",
     ],
     apex_available: [
         "com.android.tethering",
@@ -54,6 +82,7 @@
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
         "//packages/modules/Connectivity/service-t",
+        "//packages/modules/Nearby/service",
         "//frameworks/base",
 
         // Tests using hidden APIs
@@ -66,6 +95,7 @@
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Nearby/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
 }
diff --git a/framework/Android.bp b/framework/Android.bp
index de505c7..1545264 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -55,12 +55,11 @@
     ],
 }
 
-java_sdk_library {
-    name: "framework-connectivity",
+java_defaults {
+    name: "framework-connectivity-defaults",
+    defaults: ["framework-module-defaults"],
     sdk_version: "module_current",
     min_sdk_version: "30",
-    defaults: ["framework-module-defaults"],
-    installable: true,
     srcs: [
         ":framework-connectivity-sources",
         ":net-utils-framework-common-srcs",
@@ -76,6 +75,9 @@
             "frameworks/native/aidl/binder", // For PersistableBundle.aidl
         ],
     },
+    stub_only_libs: [
+        "framework-connectivity-tiramisu.stubs.module_lib",
+    ],
     impl_only_libs: [
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
@@ -83,16 +85,42 @@
     ],
     static_libs: [
         "modules-utils-build",
+        "modules-utils-preconditions",
     ],
     libs: [
+        "framework-connectivity-tiramisu.stubs.module_lib",
         "unsupportedappusage",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    apex_available: [
+        "com.android.tethering",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "framework-connectivity-pre-jarjar",
+    defaults: ["framework-connectivity-defaults"],
+    libs: [
+        // This cannot be in the defaults clause above because if it were, it would be used
+        // to generate the connectivity stubs. That would create a circular dependency
+        // because the tethering stubs depend on the connectivity stubs (e.g.,
+        // TetheringRequest depends on LinkAddress).
+        "framework-tethering.stubs.module_lib",
+    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"]
+}
+
+java_sdk_library {
+    name: "framework-connectivity",
+    defaults: ["framework-connectivity-defaults"],
+    installable: true,
+    jarjar_rules: ":connectivity-jarjar-rules",
     permitted_packages: ["android.net"],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering/apex",
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
+        "//packages/modules/Connectivity/framework-t",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service-t",
         "//frameworks/base/packages/Connectivity/service",
@@ -111,10 +139,6 @@
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-    lint: { strict_updatability_linting: true },
 }
 
 cc_library_shared {
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 8da421d..5cfab5d 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -132,6 +132,7 @@
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAccessUids();
     method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
     method public boolean hasForbiddenCapability(int);
     field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
@@ -143,6 +144,7 @@
   }
 
   public static final class NetworkCapabilities.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAccessUids(@NonNull java.util.Set<java.lang.Integer>);
     method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
   }
 
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
deleted file mode 100644
index ae6b9c3..0000000
--- a/framework/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
-rule com.android.modules.utils.** android.net.connectivity.framework.modules.utils.@1
-rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4ae3a06..4254a66 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -264,6 +264,7 @@
         mTransportInfo = null;
         mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
         mUids = null;
+        mAccessUids.clear();
         mAdministratorUids = new int[0];
         mOwnerUid = Process.INVALID_UID;
         mSSID = null;
@@ -294,6 +295,7 @@
         }
         mSignalStrength = nc.mSignalStrength;
         mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids);
+        setAccessUids(nc.mAccessUids);
         setAdministratorUids(nc.getAdministratorUids());
         mOwnerUid = nc.mOwnerUid;
         mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities;
@@ -1025,6 +1027,7 @@
         final int[] originalAdministratorUids = getAdministratorUids();
         final TransportInfo originalTransportInfo = getTransportInfo();
         final Set<Integer> originalSubIds = getSubscriptionIds();
+        final Set<Integer> originalAccessUids = new ArraySet<>(mAccessUids);
         clearAll();
         if (0 != (originalCapabilities & (1 << NET_CAPABILITY_NOT_RESTRICTED))) {
             // If the test network is not restricted, then it is only allowed to declare some
@@ -1044,6 +1047,7 @@
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
         mTransportInfo = originalTransportInfo;
+        mAccessUids.addAll(originalAccessUids);
 
         // Only retain the owner and administrator UIDs if they match the app registering the remote
         // caller that registered the network.
@@ -1809,6 +1813,86 @@
     }
 
     /**
+     * List of UIDs that can always access this network.
+     * <p>
+     * UIDs in this list have access to this network, even if the network doesn't have the
+     * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+     * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+     * This is only useful for restricted networks. For non-restricted networks it has no effect.
+     * <p>
+     * This is disallowed in {@link NetworkRequest}, and can only be set by network agents. Network
+     * agents also have restrictions on how they can set these ; they can only back a public
+     * Android API. As such, Ethernet agents can set this when backing the per-UID access API, and
+     * Telephony can set exactly one UID which has to match the manager app for the associated
+     * subscription. Failure to comply with these rules will see this member cleared.
+     * <p>
+     * This member is never null, but can be empty.
+     * @hide
+     */
+    @NonNull
+    private final ArraySet<Integer> mAccessUids = new ArraySet<>();
+
+    /**
+     * Set the list of UIDs that can always access this network.
+     * @param uids
+     * @hide
+     */
+    public void setAccessUids(@NonNull final Set<Integer> uids) {
+        // could happen with nc.set(nc), cheaper than always making a defensive copy
+        if (uids == mAccessUids) return;
+
+        Objects.requireNonNull(uids);
+        mAccessUids.clear();
+        mAccessUids.addAll(uids);
+    }
+
+    /**
+     * The list of UIDs that can always access this network.
+     *
+     * The UIDs in this list can always access this network, even if it is restricted and
+     * the UID doesn't hold the USE_RESTRICTED_NETWORKS permission. This is defined by the
+     * network agent in charge of creating the network.
+     *
+     * The UIDs are only visible to network factories and the system server, since the system
+     * server makes sure to redact them before sending a NetworkCapabilities to a process
+     * that doesn't hold the permission.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public @NonNull Set<Integer> getAccessUids() {
+        return new ArraySet<>(mAccessUids);
+    }
+
+    /** @hide */
+    // For internal clients that know what they are doing and need to avoid the performance hit
+    // of the defensive copy.
+    public @NonNull ArraySet<Integer> getAccessUidsNoCopy() {
+        return mAccessUids;
+    }
+
+    /**
+     * Test whether this UID has special permission to access this network, as per mAccessUids.
+     * @hide
+     */
+    public boolean isAccessUid(int uid) {
+        return mAccessUids.contains(uid);
+    }
+
+    /**
+     * @return whether any UID is in the list of access UIDs
+     * @hide
+     */
+    public boolean hasAccessUids() {
+        return !mAccessUids.isEmpty();
+    }
+
+    private boolean equalsAccessUids(@NonNull NetworkCapabilities other) {
+        return mAccessUids.equals(other.mAccessUids);
+    }
+
+    /**
      * The SSID of the network, or null if not applicable or unknown.
      * <p>
      * This is filled in by wifi code.
@@ -1962,6 +2046,7 @@
                 && equalsSpecifier(that)
                 && equalsTransportInfo(that)
                 && equalsUids(that)
+                && equalsAccessUids(that)
                 && equalsSSID(that)
                 && equalsOwnerUid(that)
                 && equalsPrivateDnsBroken(that)
@@ -1986,15 +2071,16 @@
                 + mSignalStrength * 29
                 + mOwnerUid * 31
                 + Objects.hashCode(mUids) * 37
-                + Objects.hashCode(mSSID) * 41
-                + Objects.hashCode(mTransportInfo) * 43
-                + Objects.hashCode(mPrivateDnsBroken) * 47
-                + Objects.hashCode(mRequestorUid) * 53
-                + Objects.hashCode(mRequestorPackageName) * 59
-                + Arrays.hashCode(mAdministratorUids) * 61
-                + Objects.hashCode(mSubIds) * 67
-                + Objects.hashCode(mUnderlyingNetworks) * 71
-                + mEnterpriseId * 73;
+                + Objects.hashCode(mAccessUids) * 41
+                + Objects.hashCode(mSSID) * 43
+                + Objects.hashCode(mTransportInfo) * 47
+                + Objects.hashCode(mPrivateDnsBroken) * 53
+                + Objects.hashCode(mRequestorUid) * 59
+                + Objects.hashCode(mRequestorPackageName) * 61
+                + Arrays.hashCode(mAdministratorUids) * 67
+                + Objects.hashCode(mSubIds) * 71
+                + Objects.hashCode(mUnderlyingNetworks) * 73
+                + mEnterpriseId * 79;
     }
 
     @Override
@@ -2022,6 +2108,7 @@
         dest.writeParcelable((Parcelable) mTransportInfo, flags);
         dest.writeInt(mSignalStrength);
         writeParcelableArraySet(dest, mUids, flags);
+        dest.writeIntArray(CollectionUtils.toIntArray(mAccessUids));
         dest.writeString(mSSID);
         dest.writeBoolean(mPrivateDnsBroken);
         dest.writeIntArray(getAdministratorUids());
@@ -2034,7 +2121,7 @@
     }
 
     public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
-        new Creator<NetworkCapabilities>() {
+            new Creator<>() {
             @Override
             public NetworkCapabilities createFromParcel(Parcel in) {
                 NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2048,6 +2135,11 @@
                 netCap.mTransportInfo = in.readParcelable(null);
                 netCap.mSignalStrength = in.readInt();
                 netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
+                final int[] accessUids = in.createIntArray();
+                netCap.mAccessUids.ensureCapacity(accessUids.length);
+                for (int uid : accessUids) {
+                    netCap.mAccessUids.add(uid);
+                }
                 netCap.mSSID = in.readString();
                 netCap.mPrivateDnsBroken = in.readBoolean();
                 netCap.setAdministratorUids(in.createIntArray());
@@ -2124,6 +2216,11 @@
                 sb.append(" Uids: <").append(mUids).append(">");
             }
         }
+
+        if (hasAccessUids()) {
+            sb.append(" AccessUids: <").append(mAccessUids).append(">");
+        }
+
         if (mOwnerUid != Process.INVALID_UID) {
             sb.append(" OwnerUid: ").append(mOwnerUid);
         }
@@ -2300,7 +2397,7 @@
 
     private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
         if (!isValidCapability(capability)) {
-            throw new IllegalArgumentException("NetworkCapability " + capability + "out of range");
+            throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
         }
     }
 
@@ -2909,6 +3006,44 @@
         }
 
         /**
+         * Set a list of UIDs that can always access this network
+         * <p>
+         * Provide a list of UIDs that can access this network even if the network doesn't have the
+         * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+         * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+         * <p>
+         * This is disallowed in {@link NetworkRequest}, and can only be set by
+         * {@link NetworkAgent}s, who hold the
+         * {@link android.Manifest.permission.NETWORK_FACTORY} permission.
+         * Network agents also have restrictions on how they can set these ; they can only back
+         * a public Android API. As such, Ethernet agents can set this when backing the per-UID
+         * access API, and Telephony can set exactly one UID which has to match the manager app for
+         * the associated subscription. Failure to comply with these rules will see this member
+         * cleared.
+         * <p>
+         * These UIDs are only visible to network factories and the system server, since the system
+         * server makes sure to redact them before sending a {@link NetworkCapabilities} instance
+         * to a process that doesn't hold the {@link android.Manifest.permission.NETWORK_FACTORY}
+         * permission.
+         * <p>
+         * This list cannot be null, but it can be empty to mean that no UID without the
+         * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission
+         * gets to access this network.
+         *
+         * @param uids the list of UIDs that can always access this network
+         * @return this builder
+         * @hide
+         */
+        @NonNull
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+        public Builder setAccessUids(@NonNull Set<Integer> uids) {
+            Objects.requireNonNull(uids);
+            mCaps.setAccessUids(uids);
+            return this;
+        }
+
+        /**
          * Set the underlying networks of this network.
          *
          * @param networks The underlying networks of this network.
diff --git a/nearby/Android.bp b/nearby/Android.bp
new file mode 100644
index 0000000..baa0740
--- /dev/null
+++ b/nearby/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Empty sources and libraries to avoid merge conflicts with downstream
+// branches
+// TODO: remove once the Nearby sources are available in this branch
+filegroup {
+    name: "framework-nearby-java-sources",
+    srcs: [],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+
+java_library {
+    name: "service-nearby",
+    srcs: [],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    apex_available: ["com.android.tethering"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/netd/Android.bp b/netd/Android.bp
new file mode 100644
index 0000000..53c1bd4
--- /dev/null
+++ b/netd/Android.bp
@@ -0,0 +1,81 @@
+//
+// 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.
+
+cc_library {
+    name: "libnetd_updatable",
+    version_script: "libnetd_updatable.map.txt",
+    stubs: {
+        versions: [
+            "1",
+        ],
+        symbol_file: "libnetd_updatable.map.txt",
+    },
+    defaults: ["netd_defaults"],
+    header_libs: [
+        "bpf_connectivity_headers",
+        "libcutils_headers",
+    ],
+    srcs: [
+        "BpfHandler.cpp",
+        "NetdUpdatable.cpp",
+    ],
+    static_libs: [
+        "libnetdutils",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    export_include_dirs: ["include"],
+    header_abi_checker: {
+        enabled: true,
+        symbol_file: "libnetd_updatable.map.txt",
+    },
+    sanitize: {
+        cfi: true,
+    },
+    apex_available: ["com.android.tethering"],
+    min_sdk_version: "30",
+}
+
+cc_test {
+    name: "netd_updatable_unit_test",
+    defaults: ["netd_defaults"],
+    test_suites: ["general-tests"],
+    require_root: true,  // required by setrlimitForTest()
+    header_libs: [
+        "bpf_connectivity_headers",
+    ],
+    srcs: [
+        "BpfHandlerTest.cpp",
+    ],
+    static_libs: [
+        "libnetd_updatable",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libnetdutils",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
new file mode 100644
index 0000000..3cd5e13
--- /dev/null
+++ b/netd/BpfHandler.cpp
@@ -0,0 +1,212 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "BpfHandler"
+
+#include "BpfHandler.h"
+
+#include <linux/bpf.h>
+
+#include <android-base/unique_fd.h>
+#include <bpf/WaitForProgsLoaded.h>
+#include <log/log.h>
+#include <netdutils/UidConstants.h>
+#include <private/android_filesystem_config.h>
+
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+namespace net {
+
+using base::unique_fd;
+using bpf::NONEXISTENT_COOKIE;
+using bpf::getSocketCookie;
+using bpf::retrieveProgram;
+using netdutils::Status;
+using netdutils::statusFromErrno;
+
+constexpr int PER_UID_STATS_ENTRIES_LIMIT = 500;
+// At most 90% of the stats map may be used by tagged traffic entries. This ensures
+// that 10% of the map is always available to count untagged traffic, one entry per UID.
+// Otherwise, apps would be able to avoid data usage accounting entirely by filling up the
+// map with tagged traffic entries.
+constexpr int TOTAL_UID_STATS_ENTRIES_LIMIT = STATS_MAP_SIZE * 0.9;
+
+static_assert(STATS_MAP_SIZE - TOTAL_UID_STATS_ENTRIES_LIMIT > 100,
+              "The limit for stats map is to high, stats data may be lost due to overflow");
+
+static Status attachProgramToCgroup(const char* programPath, const unique_fd& cgroupFd,
+                                    bpf_attach_type type) {
+    unique_fd cgroupProg(retrieveProgram(programPath));
+    if (cgroupProg == -1) {
+        int ret = errno;
+        ALOGE("Failed to get program from %s: %s", programPath, strerror(ret));
+        return statusFromErrno(ret, "cgroup program get failed");
+    }
+    if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+        int ret = errno;
+        ALOGE("Program from %s attach failed: %s", programPath, strerror(ret));
+        return statusFromErrno(ret, "program attach failed");
+    }
+    return netdutils::status::ok;
+}
+
+static Status initPrograms(const char* cg2_path) {
+    unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (cg_fd == -1) {
+        int ret = errno;
+        ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
+        return statusFromErrno(ret, "Open the cgroup directory failed");
+    }
+    RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
+    RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
+
+    // For the devices that support cgroup socket filter, the socket filter
+    // should be loaded successfully by bpfloader. So we attach the filter to
+    // cgroup if the program is pinned properly.
+    // TODO: delete the if statement once all devices should support cgroup
+    // socket filter (ie. the minimum kernel version required is 4.14).
+    if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+        RETURN_IF_NOT_OK(
+                attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+    }
+    return netdutils::status::ok;
+}
+
+BpfHandler::BpfHandler()
+    : mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
+      mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+
+BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
+    : mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+
+Status BpfHandler::init(const char* cg2_path) {
+    // Make sure BPF programs are loaded before doing anything
+    android::bpf::waitForProgsLoaded();
+    ALOGI("BPF programs are loaded");
+
+    RETURN_IF_NOT_OK(initPrograms(cg2_path));
+    RETURN_IF_NOT_OK(initMaps());
+
+    return netdutils::status::ok;
+}
+
+Status BpfHandler::initMaps() {
+    std::lock_guard guard(mMutex);
+    RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
+    RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
+    RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
+    RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
+    RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
+                                                  BPF_ANY));
+    RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+
+    return netdutils::status::ok;
+}
+
+bool BpfHandler::hasUpdateDeviceStatsPermission(uid_t uid) {
+    // This implementation is the same logic as method ActivityManager#checkComponentPermission.
+    // It implies that the real uid can never be the same as PER_USER_RANGE.
+    uint32_t appId = uid % PER_USER_RANGE;
+    auto permission = mUidPermissionMap.readValue(appId);
+    if (permission.ok() && (permission.value() & BPF_PERMISSION_UPDATE_DEVICE_STATS)) {
+        return true;
+    }
+    return ((appId == AID_ROOT) || (appId == AID_SYSTEM) || (appId == AID_DNS));
+}
+
+int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+    std::lock_guard guard(mMutex);
+    if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) {
+        return -EPERM;
+    }
+
+    uint64_t sock_cookie = getSocketCookie(sockFd);
+    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+    UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
+
+    uint32_t totalEntryCount = 0;
+    uint32_t perUidEntryCount = 0;
+    // Now we go through the stats map and count how many entries are associated
+    // with chargeUid. If the uid entry hit the limit for each chargeUid, we block
+    // the request to prevent the map from overflow. It is safe here to iterate
+    // over the map since when mMutex is hold, system server cannot toggle
+    // the live stats map and clean it. So nobody can delete entries from the map.
+    const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
+                                              const StatsKey& key,
+                                              const BpfMap<StatsKey, StatsValue>&) {
+        if (key.uid == chargeUid) {
+            perUidEntryCount++;
+        }
+        totalEntryCount++;
+        return base::Result<void>();
+    };
+    auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+    if (!configuration.ok()) {
+        ALOGE("Failed to get current configuration: %s, fd: %d",
+              strerror(configuration.error().code()), mConfigurationMap.getMap().get());
+        return -configuration.error().code();
+    }
+    if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+        ALOGE("unknown configuration value: %d", configuration.value());
+        return -EINVAL;
+    }
+
+    BpfMap<StatsKey, StatsValue>& currentMap =
+            (configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
+    base::Result<void> res = currentMap.iterate(countUidStatsEntries);
+    if (!res.ok()) {
+        ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
+              strerror(res.error().code()));
+        return -res.error().code();
+    }
+
+    if (totalEntryCount > mTotalUidStatsEntriesLimit ||
+        perUidEntryCount > mPerUidStatsEntriesLimit) {
+        ALOGE("Too many stats entries in the map, total count: %u, chargeUid(%u) count: %u,"
+              " blocking tag request to prevent map overflow",
+              totalEntryCount, chargeUid, perUidEntryCount);
+        return -EMFILE;
+    }
+    // Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
+    // flag so it will insert a new entry to the map if that value doesn't exist
+    // yet. And update the tag if there is already a tag stored. Since the eBPF
+    // program in kernel only read this map, and is protected by rcu read lock. It
+    // should be fine to cocurrently update the map while eBPF program is running.
+    res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
+    if (!res.ok()) {
+        ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.error().code()),
+              mCookieTagMap.getMap().get());
+        return -res.error().code();
+    }
+    return 0;
+}
+
+int BpfHandler::untagSocket(int sockFd) {
+    std::lock_guard guard(mMutex);
+    uint64_t sock_cookie = getSocketCookie(sockFd);
+
+    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+    base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
+    if (!res.ok()) {
+        ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+        return -res.error().code();
+    }
+    return 0;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
new file mode 100644
index 0000000..2ede1c1
--- /dev/null
+++ b/netd/BpfHandler.h
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include <mutex>
+
+#include <netdutils/Status.h>
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+
+using android::bpf::BpfMap;
+
+namespace android {
+namespace net {
+
+class BpfHandler {
+  public:
+    BpfHandler();
+    BpfHandler(const BpfHandler&) = delete;
+    BpfHandler& operator=(const BpfHandler&) = delete;
+    netdutils::Status init(const char* cg2_path);
+    /*
+     * Tag the socket with the specified tag and uid. In the qtaguid module, the
+     * first tag request that grab the spinlock of rb_tree can update the tag
+     * information first and other request need to wait until it finish. All the
+     * tag request will be addressed in the order of they obtaining the spinlock.
+     * In the eBPF implementation, the kernel will try to update the eBPF map
+     * entry with the tag request. And the hashmap update process is protected by
+     * the spinlock initialized with the map. So the behavior of two modules
+     * should be the same. No additional lock needed.
+     */
+    int tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid);
+
+    /*
+     * The untag process is similar to tag socket and both old qtaguid module and
+     * new eBPF module have spinlock inside the kernel for concurrent update. No
+     * external lock is required.
+     */
+    int untagSocket(int sockFd);
+
+  private:
+    // For testing
+    BpfHandler(uint32_t perUidLimit, uint32_t totalLimit);
+
+    netdutils::Status initMaps();
+    bool hasUpdateDeviceStatsPermission(uid_t uid);
+
+    BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+    BpfMap<StatsKey, StatsValue> mStatsMapA;
+    BpfMap<StatsKey, StatsValue> mStatsMapB;
+    BpfMap<uint32_t, uint8_t> mConfigurationMap;
+    BpfMap<uint32_t, uint8_t> mUidPermissionMap;
+
+    std::mutex mMutex;
+
+    // The limit on the number of stats entries a uid can have in the per uid stats map. BpfHandler
+    // will block that specific uid from tagging new sockets after the limit is reached.
+    const uint32_t mPerUidStatsEntriesLimit;
+
+    // The limit on the total number of stats entries in the per uid stats map. BpfHandler will
+    // block all tagging requests after the limit is reached.
+    const uint32_t mTotalUidStatsEntriesLimit;
+
+    // For testing
+    friend class BpfHandlerTest;
+};
+
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
new file mode 100644
index 0000000..db59c7c
--- /dev/null
+++ b/netd/BpfHandlerTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright 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.
+ *
+ * BpfHandlerTest.cpp - unit tests for BpfHandler.cpp
+ */
+
+#include <sys/socket.h>
+
+#include <gtest/gtest.h>
+
+#include "BpfHandler.h"
+
+using namespace android::bpf;  // NOLINT(google-build-using-namespace): exempted
+
+namespace android {
+namespace net {
+
+using base::Result;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr int TEST_COOKIE = 1;
+constexpr uid_t TEST_UID = 10086;
+constexpr uid_t TEST_UID2 = 54321;
+constexpr uint32_t TEST_TAG = 42;
+constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr uint32_t TEST_PER_UID_STATS_ENTRIES_LIMIT = 3;
+constexpr uint32_t TEST_TOTAL_UID_STATS_ENTRIES_LIMIT = 7;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+
+class BpfHandlerTest : public ::testing::Test {
+  protected:
+    BpfHandlerTest()
+        : mBh(TEST_PER_UID_STATS_ENTRIES_LIMIT, TEST_TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+    BpfHandler mBh;
+    BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+    BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+    BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+    BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+
+    void SetUp() {
+        std::lock_guard guard(mBh.mMutex);
+        ASSERT_EQ(0, setrlimitForTest());
+
+        mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
+                                          TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeCookieTagMap);
+
+        mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
+                                       TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeStatsMapA);
+
+        mFakeConfigurationMap.reset(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+        ASSERT_VALID(mFakeConfigurationMap);
+
+        mFakeUidPermissionMap.reset(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeUidPermissionMap);
+
+        mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+        ASSERT_VALID(mBh.mCookieTagMap);
+        mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+        ASSERT_VALID(mBh.mStatsMapA);
+        mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+        ASSERT_VALID(mBh.mConfigurationMap);
+        // Always write to stats map A by default.
+        ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+                                                          SELECT_MAP_A, BPF_ANY));
+        mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+        ASSERT_VALID(mBh.mUidPermissionMap);
+    }
+
+    int dupFd(const android::base::unique_fd& mapFd) {
+        return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+    }
+
+    int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
+                          uid_t realUid) {
+        int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        EXPECT_LE(0, sock);
+        *cookie = getSocketCookie(sock);
+        EXPECT_NE(NONEXISTENT_COOKIE, *cookie);
+        EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, realUid));
+        return sock;
+    }
+
+    void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
+        Result<UidTagValue> tagResult = mFakeCookieTagMap.readValue(cookie);
+        ASSERT_RESULT_OK(tagResult);
+        EXPECT_EQ(uid, tagResult.value().uid);
+        EXPECT_EQ(tag, tagResult.value().tag);
+    }
+
+    void expectNoTag(uint64_t cookie) { EXPECT_FALSE(mFakeCookieTagMap.readValue(cookie).ok()); }
+
+    void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
+        UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
+        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};
+        EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+        key->tag = 0;
+        EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+        // put tag information back to statsKey
+        key->tag = tag;
+    }
+
+    template <class Key, class Value>
+    void expectMapEmpty(BpfMap<Key, Value>& map) {
+        auto isEmpty = map.isEmpty();
+        EXPECT_RESULT_OK(isEmpty);
+        EXPECT_TRUE(isEmpty.value());
+    }
+
+    void expectTagSocketReachLimit(uint32_t tag, uint32_t uid) {
+        int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        EXPECT_LE(0, sock);
+        if (sock < 0) return;
+        uint64_t sockCookie = getSocketCookie(sock);
+        EXPECT_NE(NONEXISTENT_COOKIE, sockCookie);
+        EXPECT_EQ(-EMFILE, mBh.tagSocket(sock, tag, uid, uid));
+        expectNoTag(sockCookie);
+
+        // Delete stats entries then tag socket success
+        StatsKey key = {.uid = uid, .tag = 0, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+        ASSERT_RESULT_OK(mFakeStatsMapA.deleteValue(key));
+        EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, uid));
+        expectUidTag(sockCookie, uid, tag);
+    }
+};
+
+TEST_F(BpfHandlerTest, TestTagSocketV4) {
+    uint64_t sockCookie;
+    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.untagSocket(v4socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestReTagSocket) {
+    uint64_t sockCookie;
+    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1, TEST_UID + 1));
+    expectUidTag(sockCookie, TEST_UID + 1, TEST_TAG + 1);
+}
+
+TEST_F(BpfHandlerTest, TestTagTwoSockets) {
+    uint64_t sockCookie1;
+    uint64_t sockCookie2;
+    int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
+    setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie1, TEST_UID, TEST_TAG);
+    expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.untagSocket(v4socket1));
+    expectNoTag(sockCookie1);
+    expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+    ASSERT_FALSE(mFakeCookieTagMap.getNextKey(sockCookie2).ok());
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketV6) {
+    uint64_t sockCookie;
+    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.untagSocket(v6socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagInvalidSocket) {
+    int invalidSocket = -1;
+    ASSERT_GT(0, mBh.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithoutPermission) {
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_NE(-1, sock);
+    ASSERT_EQ(-EPERM, mBh.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithPermission) {
+    // Grant permission to real uid. In practice, the uid permission map will be updated by
+    // TrafficController::setPermissionForUids().
+    uid_t realUid = TEST_UID2;
+    ASSERT_RESULT_OK(mFakeUidPermissionMap.writeValue(realUid,
+                     BPF_PERMISSION_UPDATE_DEVICE_STATS, BPF_ANY));
+
+    // Tag a socket to a different uid other then realUid.
+    uint64_t sockCookie;
+    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, realUid);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    EXPECT_EQ(0, mBh.untagSocket(v6socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestUntagInvalidSocket) {
+    int invalidSocket = -1;
+    ASSERT_GT(0, mBh.untagSocket(invalidSocket));
+    int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_GT(0, mBh.untagSocket(v4socket));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachLimitFail) {
+    uid_t uid = TEST_UID;
+    StatsKey tagStatsMapKey[3];
+    for (int i = 0; i < 3; i++) {
+        uint64_t cookie = TEST_COOKIE + i;
+        uint32_t tag = TEST_TAG + i;
+        populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+    }
+    expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachTotalLimitFail) {
+    StatsKey tagStatsMapKey[4];
+    for (int i = 0; i < 4; i++) {
+        uint64_t cookie = TEST_COOKIE + i;
+        uint32_t tag = TEST_TAG + i;
+        uid_t uid = TEST_UID + i;
+        populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+    }
+    expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
new file mode 100644
index 0000000..f0997fc
--- /dev/null
+++ b/netd/NetdUpdatable.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NetdUpdatable"
+
+#include "NetdUpdatable.h"
+
+#include <android-base/logging.h>
+#include <netdutils/Status.h>
+
+#include "NetdUpdatablePublic.h"
+
+int libnetd_updatable_init(const char* cg2_path) {
+    android::base::InitLogging(/*argv=*/nullptr);
+    LOG(INFO) << __func__ << ": Initializing";
+
+    android::net::gNetdUpdatable = android::net::NetdUpdatable::getInstance();
+    android::netdutils::Status ret = android::net::gNetdUpdatable->mBpfHandler.init(cg2_path);
+    if (!android::netdutils::isOk(ret)) {
+        LOG(ERROR) << __func__ << ": BPF handler init failed";
+        return -ret.code();
+    }
+    return 0;
+}
+
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+    if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+    return android::net::gNetdUpdatable->mBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
+}
+
+int libnetd_updatable_untagSocket(int sockFd) {
+    if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+    return android::net::gNetdUpdatable->mBpfHandler.untagSocket(sockFd);
+}
+
+namespace android {
+namespace net {
+
+NetdUpdatable* gNetdUpdatable = nullptr;
+
+NetdUpdatable* NetdUpdatable::getInstance() {
+    // Instantiated on first use.
+    static NetdUpdatable instance;
+    return &instance;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/netd/NetdUpdatable.h b/netd/NetdUpdatable.h
new file mode 100644
index 0000000..333037f
--- /dev/null
+++ b/netd/NetdUpdatable.h
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include "BpfHandler.h"
+
+namespace android {
+namespace net {
+
+class NetdUpdatable {
+  public:
+    NetdUpdatable() = default;
+    NetdUpdatable(const NetdUpdatable&) = delete;
+    NetdUpdatable& operator=(const NetdUpdatable&) = delete;
+    static NetdUpdatable* getInstance();
+
+    BpfHandler mBpfHandler;
+};
+
+extern NetdUpdatable* gNetdUpdatable;
+
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/netd/include/NetdUpdatablePublic.h b/netd/include/NetdUpdatablePublic.h
new file mode 100644
index 0000000..1ca5ea2
--- /dev/null
+++ b/netd/include/NetdUpdatablePublic.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * Initial function for libnetd_updatable library.
+ *
+ * The function uses |cg2_path| as cgroup v2 mount location to attach BPF programs so that the
+ * kernel can record packet number, size, etc. in BPF maps when packets pass through, and let user
+ * space retrieve statistics.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on
+ * failure.
+ */
+int libnetd_updatable_init(const char* cg2_path);
+
+/*
+ * Set the socket tag and owning UID for traffic statistics on the specified socket. Permission
+ * check is performed based on the |realUid| before socket tagging.
+ *
+ * The |sockFd| is a file descriptor of the socket that needs to tag. The |tag| is the mark to tag.
+ * It can be an arbitrary value in uint32_t range. The |chargeUid| is owning uid which will be
+ * tagged along with the |tag|. The |realUid| is an effective uid of the calling process, which is
+ * used for permission check before socket tagging.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid,
+                                                       uid_t realUid);
+
+/*
+ * Untag a network socket. Future traffic on this socket will no longer be associated with any
+ * previously configured tag and uid.
+ *
+ * The |sockFd| is a file descriptor of the socket that wants to untag.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_untagSocket(int sockFd);
+
+__END_DECLS
\ No newline at end of file
diff --git a/netd/libnetd_updatable.map.txt b/netd/libnetd_updatable.map.txt
new file mode 100644
index 0000000..dcb11a1
--- /dev/null
+++ b/netd/libnetd_updatable.map.txt
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+# This lists the entry points visible to applications that use the libnetd_updatable
+# library. Other entry points present in the library won't be usable.
+
+LIBNETD_UPDATABLE {
+  global:
+    libnetd_updatable_init; # apex
+    libnetd_updatable_tagSocket; # apex
+    libnetd_updatable_untagSocket; # apex
+  local:
+    *;
+};
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 48c74c6..f33be63 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -36,15 +36,17 @@
     ],
     libs: [
         "framework-annotations-lib",
-        "framework-connectivity.impl",
+        "framework-connectivity-pre-jarjar",
         "framework-connectivity-tiramisu.impl",
         "service-connectivity-pre-jarjar",
         "unsupportedappusage",
     ],
     static_libs: [
-        "modules-utils-build",
+        // Do not add static_libs here if they are already included in framework-connectivity
+        // or in service-connectivity. They are not necessary (included via
+        // service-connectivity-pre-jarjar), and in the case of code that is already in
+        // framework-connectivity, the classes would be included in the apex twice.
         "modules-utils-statemachine",
-        "net-utils-framework-common",
     ],
     apex_available: [
         "com.android.tethering",
diff --git a/service/Android.bp b/service/Android.bp
index 33d4b66..6d187ba 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,9 +20,9 @@
 }
 
 // The library name match the service-connectivity jarjar rules that put the JNI utils in the
-// com.android.connectivity.com.android.net.module.util package.
+// android.net.connectivity.com.android.net.module.util package.
 cc_library_shared {
-    name: "libcom_android_connectivity_com_android_net_module_util_jni",
+    name: "libandroid_net_connectivity_com_android_net_module_util_jni",
     min_sdk_version: "30",
     cflags: [
         "-Wall",
@@ -57,19 +57,24 @@
         "-Wthread-safety",
     ],
     srcs: [
-        "jni/com_android_server_TestNetworkService.cpp",
+        "jni/com_android_server_BpfNetMaps.cpp",
         "jni/com_android_server_connectivity_ClatCoordinator.cpp",
+        "jni/com_android_server_TestNetworkService.cpp",
         "jni/onload.cpp",
     ],
     stl: "libc++_static",
     header_libs: [
+        "bpf_connectivity_headers",
         "libbase_headers",
     ],
     static_libs: [
         "libbase",
         "libclat",
         "libip_checksum",
+        "libnetdutils",
         "libnetjniutils",
+        "libtraffic_controller",
+        "netd_aidl_interface-lateststable-ndk",
     ],
     shared_libs: [
         "liblog",
@@ -93,20 +98,20 @@
     ],
     libs: [
         "framework-annotations-lib",
-        "framework-connectivity.impl",
+        "framework-connectivity-pre-jarjar",
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
         "unsupportedappusage",
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        // Do not add libs here if they are already included
+        // in framework-connectivity
         "dnsresolver_aidl_interface-V9-java",
-        "modules-utils-build",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
         "net-utils-device-common-netlink",
-        "net-utils-framework-common",
         "netd-client",
         "networkstack-client",
         "PlatformProperties",
@@ -152,8 +157,9 @@
     static_libs: [
         "service-connectivity-pre-jarjar",
         "service-connectivity-tiramisu-pre-jarjar",
+        "service-nearby",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    jarjar_rules: ":connectivity-jarjar-rules",
     apex_available: [
         "com.android.tethering",
     ],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index f658a5e..e3b26fd 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,6 +1,15 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
 rule android.sysprop.** com.android.connectivity.@0
-rule com.android.net.module.util.** com.android.connectivity.@0
-rule com.android.modules.utils.** com.android.connectivity.@0
 
 # internal util classes from framework-connectivity-shared-srcs
 rule android.util.LocalLog* com.android.connectivity.@0
@@ -23,9 +32,6 @@
 rule android.net.ResolverParamsParcel* com.android.connectivity.@0
 # Also includes netd event listener AIDL, but this is handled by netd-client rules
 
-# From net-utils-device-common
-rule android.net.NetworkFactory* com.android.connectivity.@0
-
 # From netd-client (newer AIDLs should go to android.net.netd.aidl)
 rule android.net.netd.aidl.** com.android.connectivity.@0
 # Avoid including android.net.INetdEventCallback, used in tests but not part of the module
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 07ae31c..2f09e55 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -30,10 +30,10 @@
     }
 
     if (register_com_android_net_module_util_BpfMap(env,
-            "com/android/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+            "android/net/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
 
     if (register_com_android_net_module_util_TcUtils(env,
-            "com/android/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
+            "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
 
     return JNI_VERSION_1_6;
 }
diff --git a/service/native/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
similarity index 98%
rename from service/native/jni/com_android_server_BpfNetMaps.cpp
rename to service/jni/com_android_server_BpfNetMaps.cpp
index 7ab4d46..bde52a5 100644
--- a/service/native/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -16,17 +16,17 @@
 
 #define LOG_TAG "TrafficControllerJni"
 
+#include "TrafficController.h"
+
+#include <bpf_shared.h>
 #include <jni.h>
+#include <log/log.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <net/if.h>
 #include <vector>
 
-#include "TrafficController.h"
-#include "android-base/logging.h"
-#include "bpf_shared.h"
-#include "utils/Log.h"
 
 using android::net::TrafficController;
 using android::netdutils::Status;
@@ -39,7 +39,8 @@
 namespace android {
 
 static void native_init(JNIEnv* env, jobject clazz) {
-  Status status = mTc.start();
+  // start is still being called by netd
+  Status status = mTc.initMaps();
    if (!isOk(status)) {
     ALOGE("%s failed", __func__);
   }
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index 1a0de32..4efd0e1 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -98,7 +98,7 @@
     {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
 };
 
-int register_android_server_TestNetworkService(JNIEnv* env) {
+int register_com_android_server_TestNetworkService(JNIEnv* env) {
     return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods,
                                     NELEM(gMethods));
 }
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index b445462..ee512ec 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -519,7 +519,7 @@
          (void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
 };
 
-int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
     return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
                                     gMethods, NELEM(gMethods));
 }
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 04d9671..facdad7 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -19,8 +19,9 @@
 
 namespace android {
 
-int register_android_server_TestNetworkService(JNIEnv* env);
-int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
+int register_com_android_server_TestNetworkService(JNIEnv* env);
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
+int register_com_android_server_BpfNetMaps(JNIEnv* env);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
@@ -29,11 +30,15 @@
         return JNI_ERR;
     }
 
-    if (register_android_server_TestNetworkService(env) < 0) {
+    if (register_com_android_server_TestNetworkService(env) < 0) {
         return JNI_ERR;
     }
 
-    if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
+    if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
+        return JNI_ERR;
+    }
+
+    if (register_com_android_server_BpfNetMaps(env) < 0) {
         return JNI_ERR;
     }
 
diff --git a/service/native/Android.bp b/service/native/Android.bp
index b49457d..0e805e2 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -52,37 +52,6 @@
     min_sdk_version: "30",
 }
 
-cc_library_shared {
-    name: "libtraffic_controller_jni",
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wthread-safety",
-    ],
-    srcs: [
-        "jni/*.cpp",
-    ],
-    header_libs: [
-        "bpf_connectivity_headers",
-    ],
-    static_libs: [
-        "libnetdutils",
-        "libtraffic_controller",
-        "netd_aidl_interface-lateststable-ndk",
-    ],
-    shared_libs: [
-        "libbase",
-        "liblog",
-        "libutils",
-        "libnativehelper",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-    min_sdk_version: "30",
-}
-
 cc_test {
     name: "traffic_controller_unit_test",
     test_suites: ["general-tests"],
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 97fcc43..51dd502 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -683,8 +683,6 @@
     }
 }
 
-const String16 TrafficController::DUMP_KEYWORD = String16("trafficcontroller");
-
 void TrafficController::dump(DumpWriter& dw, bool verbose) {
     std::lock_guard guard(mMutex);
     ScopedIndent indentTop(dw);
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index c050871..2d89344 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -25,7 +25,6 @@
 #include "netdutils/DumpWriter.h"
 #include "netdutils/NetlinkListener.h"
 #include "netdutils/StatusOr.h"
-#include "utils/String16.h"
 
 namespace android {
 namespace net {
@@ -33,12 +32,22 @@
 using netdutils::StatusOr;
 
 class TrafficController {
-  public:
+    // TODO: marking this private for right now, as start is already called by
+    // netd. start() calls initMaps(), initPrograms(), and sets up the socket
+    // destroy listener. Both initPrograms() and setting up the socket destroy
+    // listener should only be done once.
     /*
      * Initialize the whole controller
      */
     netdutils::Status start();
 
+  public:
+    static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
+
+    // TODO: marking this public for right now, as start() is already called by
+    // netd.
+    netdutils::Status initMaps() EXCLUDES(mMutex);
+
     int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
 
     /*
@@ -84,7 +93,6 @@
 
     netdutils::Status updateUidOwnerMap(const uint32_t uid,
                                         UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex);
-    static const String16 DUMP_KEYWORD;
 
     int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
 
@@ -187,8 +195,6 @@
 
     std::mutex mMutex;
 
-    netdutils::Status initMaps() EXCLUDES(mMutex);
-
     // Keep track of uids that have permission UPDATE_DEVICE_STATS so we don't
     // need to call back to system server for permission check.
     std::set<uid_t> mPrivilegedUser GUARDED_BY(mMutex);
diff --git a/service/native/jni/onload.cpp b/service/native/jni/onload.cpp
deleted file mode 100644
index df7c77b..0000000
--- a/service/native/jni/onload.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "TrafficControllerJni"
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "utils/Log.h"
-
-namespace android {
-
-int register_com_android_server_BpfNetMaps(JNIEnv* env);
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
-    JNIEnv *env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        ALOGE("%s: ERROR: GetEnv failed", __func__);
-        return JNI_ERR;
-    }
-
-    if (register_com_android_server_BpfNetMaps(env) < 0)
-      return JNI_ERR;
-
-    return JNI_VERSION_1_6;
-}
-
-}; // namespace android
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 9d89788..e444a12 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import android.net.INetd;
+import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.system.Os;
 import android.util.Log;
@@ -27,10 +29,19 @@
  */
 public class BpfNetMaps {
     private static final String TAG = "BpfNetMaps";
+    private final INetd mNetd;
+    // TODO: change USE_JNI to SdkLevel.isAtLeastT()
+    private static final boolean USE_JNI = false;
 
     static {
-        System.loadLibrary("traffic_controller_jni");
-        native_init();
+        if (USE_JNI) {
+            System.loadLibrary("traffic_controller_jni");
+            native_init();
+        }
+    }
+
+    public BpfNetMaps(INetd netd) {
+        mNetd = netd;
     }
 
    /**
@@ -41,6 +52,14 @@
     *         cause of the failure.
     */
     public void addNaughtyApp(final int uid) {
+        if (!USE_JNI) {
+            try {
+                mNetd.bandwidthAddNaughtyApp(uid);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_addNaughtyApp(uid);
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to add naughty app: "
@@ -56,6 +75,14 @@
     *         cause of the failure.
     */
     public void removeNaughtyApp(final int uid) {
+        if (!USE_JNI) {
+            try {
+                mNetd.bandwidthRemoveNaughtyApp(uid);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_removeNaughtyApp(uid);
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to remove naughty app: "
@@ -71,6 +98,14 @@
     *         cause of the failure.
     */
     public void addNiceApp(final int uid) {
+        if (!USE_JNI) {
+            try {
+                mNetd.bandwidthAddNiceApp(uid);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_addNiceApp(uid);
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to add nice app: "
@@ -86,6 +121,14 @@
     *         cause of the failure.
     */
     public void removeNiceApp(final int uid) {
+        if (!USE_JNI) {
+            try {
+                mNetd.bandwidthRemoveNiceApp(uid);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_removeNiceApp(uid);
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to remove nice app: "
@@ -102,6 +145,14 @@
     *         cause of the failure.
     */
     public void setChildChain(final int childChain, final boolean enable) {
+        if (!USE_JNI) {
+            try {
+                mNetd.firewallEnableChildChain(childChain, enable);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_setChildChain(childChain, enable);
         if (err != 0) {
             throw new ServiceSpecificException(-err, "Unable to set child chain: "
@@ -124,6 +175,14 @@
      */
     public int replaceUidChain(final String chainName, final boolean isAllowlist,
             final int[] uids) {
+        if (!USE_JNI) {
+            try {
+                mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return 0;
+        }
         final int err = native_replaceUidChain(chainName, isAllowlist, uids);
         if (err != 0) {
             Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
@@ -140,8 +199,15 @@
     * @throws ServiceSpecificException in case of failure, with an error code indicating the
     *         cause of the failure.
     */
-    public void setUidRule(final int childChain, final int uid,
-            final int firewallRule) {
+    public void setUidRule(final int childChain, final int uid, final int firewallRule) {
+        if (!USE_JNI) {
+            try {
+                mNetd.firewallSetUidRule(childChain, uid, firewallRule);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_setUidRule(childChain, uid, firewallRule);
         if (err != 0) {
             throw new ServiceSpecificException(-err, "Unable to set uid rule: "
@@ -166,6 +232,14 @@
      *         cause of the failure.
      */
     public void addUidInterfaceRules(final String ifName, final int[] uids) {
+        if (!USE_JNI) {
+            try {
+                mNetd.firewallAddUidInterfaceRules(ifName, uids);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception when updating permissions: " + e);
+            }
+            return;
+        }
         final int err = native_addUidInterfaceRules(ifName, uids);
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to add uid interface rules: "
@@ -184,6 +258,14 @@
      *         cause of the failure.
      */
     public void removeUidInterfaceRules(final int[] uids) {
+        if (!USE_JNI) {
+            try {
+                mNetd.firewallRemoveUidInterfaceRules(uids);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception when updating permissions: " + e);
+            }
+            return;
+        }
         final int err = native_removeUidInterfaceRules(uids);
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to remove uid interface rules: "
@@ -197,6 +279,14 @@
     *         cause of the failure.
     */
     public void swapActiveStatsMap() {
+        if (!USE_JNI) {
+            try {
+                mNetd.trafficSwapActiveStatsMap();
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+            return;
+        }
         final int err = native_swapActiveStatsMap();
         if (err != 0) {
             throw new ServiceSpecificException(err, "Unable to swap active stats map: "
@@ -213,8 +303,16 @@
     *                   revoke all permissions for the uids.
     * @param uids uid of users to grant permission
     */
-    public void setNetPermForUids(final int permission, final int[] uids) {
-        native_setPermissionForUids(permission, uids);
+    public void setNetPermForUids(final int permissions, final int[] uids) {
+        if (!USE_JNI) {
+            try {
+                mNetd.trafficSetNetPermForUids(permissions, uids);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Pass appId list of special permission failed." + e);
+            }
+            return;
+        }
+        native_setPermissionForUids(permissions, uids);
     }
 
     /**
@@ -255,7 +353,7 @@
     private native int native_addUidInterfaceRules(String ifName, int[] uids);
     private native int native_removeUidInterfaceRules(int[] uids);
     private native int native_swapActiveStatsMap();
-    private native void native_setPermissionForUids(int permission, int[] uids);
+    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 fa7dfa9..a453270 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -249,6 +249,7 @@
 import com.android.net.module.util.PermissionUtils;
 import com.android.net.module.util.netlink.InetDiagMessage;
 import com.android.server.connectivity.AutodestructReference;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ConnectivityFlags;
 import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
@@ -396,6 +397,7 @@
     private NetworkStatsManager mStatsManager;
     private NetworkPolicyManager mPolicyManager;
     private final NetdCallback mNetdCallback;
+    private final BpfNetMaps mBpfNetMaps;
 
     /**
      * TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
@@ -438,8 +440,6 @@
      * Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
      * The default request uses PREFERENCE_ORDER_DEFAULT.
      */
-    // Bound for the lowest valid preference order.
-    static final int PREFERENCE_ORDER_LOWEST = 999;
     // Used when sending to netd to code for "no order".
     static final int PREFERENCE_ORDER_NONE = 0;
     // Order for requests that don't code for a per-app preference. As it is
@@ -447,11 +447,6 @@
     // PREFERENCE_ORDER_NONE when sending to netd.
     @VisibleForTesting
     static final int PREFERENCE_ORDER_INVALID = Integer.MAX_VALUE;
-    // Order for the default internet request. Since this must always have the
-    // lowest priority, its value is larger than the largest acceptable value. As
-    // it is out of the valid range, the corresponding order should be
-    // PREFERENCE_ORDER_NONE when sending to netd.
-    static final int PREFERENCE_ORDER_DEFAULT = 1000;
     // As a security feature, VPNs have the top priority.
     static final int PREFERENCE_ORDER_VPN = 0; // Netd supports only 0 for VPN.
     // Order of per-app OEM preference. See {@link #setOemNetworkPreference}.
@@ -466,6 +461,13 @@
     // See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
     @VisibleForTesting
     static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+    // Preference order that signifies the network shouldn't be set as a default network for
+    // the UIDs, only give them access to it. TODO : replace this with a boolean
+    // in NativeUidRangeConfig
+    @VisibleForTesting
+    static final int PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT = 999;
+    // Bound for the lowest valid preference order.
+    static final int PREFERENCE_ORDER_LOWEST = 999;
 
     /**
      * used internally to clear a wakelock when transitioning
@@ -593,6 +595,13 @@
     // Handle private DNS validation status updates.
     private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
 
+    /**
+     * used to remove a network request, either a listener or a real request and call unavailable
+     * arg1 = UID of caller
+     * obj  = NetworkRequest
+     */
+    private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
+
      /**
       * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
       * been tested.
@@ -760,6 +769,7 @@
     private Set<String> mWolSupportedInterfaces;
 
     private final TelephonyManager mTelephonyManager;
+    private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
     private final AppOpsManager mAppOpsManager;
 
     private final LocationPermissionChecker mLocationPermissionChecker;
@@ -1335,6 +1345,15 @@
             return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
                     TETHERING_MODULE_NAME, defaultEnabled);
         }
+
+        /**
+         * Get the BpfNetMaps implementation to use in ConnectivityService.
+         * @param netd
+         * @return BpfNetMaps implementation.
+         */
+        public BpfNetMaps getBpfNetMaps(INetd netd) {
+            return new BpfNetMaps(netd);
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -1403,9 +1422,16 @@
         mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
+        mBpfNetMaps = mDeps.getBpfNetMaps(netd);
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+        if (SdkLevel.isAtLeastT()) {
+            mCarrierPrivilegeAuthenticator =
+                    new CarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+        } else {
+            mCarrierPrivilegeAuthenticator = null;
+        }
 
         // To ensure uid state is synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -1430,7 +1456,7 @@
 
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
 
-        mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
+        mPermissionMonitor = new PermissionMonitor(mContext, mNetd, mBpfNetMaps);
 
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
         // Listen for user add/removes to inform PermissionMonitor.
@@ -2084,6 +2110,7 @@
         newNc.setAdministratorUids(new int[0]);
         if (!checkAnyPermissionOf(
                 callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+            newNc.setAccessUids(new ArraySet<>());
             newNc.setSubscriptionIds(Collections.emptySet());
         }
 
@@ -3333,18 +3360,8 @@
 
             switch (msg.what) {
                 case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
-                    NetworkCapabilities networkCapabilities = (NetworkCapabilities) arg.second;
-                    if (networkCapabilities.hasConnectivityManagedCapability()) {
-                        Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
-                    }
-                    if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-                        // Make sure the original object is not mutated. NetworkAgent normally
-                        // makes a copy of the capabilities when sending the message through
-                        // the Messenger, but if this ever changes, not making a defensive copy
-                        // here will give attack vectors to clients using this code path.
-                        networkCapabilities = new NetworkCapabilities(networkCapabilities);
-                        networkCapabilities.restrictCapabilitiesForTestNetwork(nai.creatorUid);
-                    }
+                    final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
+                            (NetworkCapabilities) arg.second);
                     processCapabilitiesFromAgent(nai, networkCapabilities);
                     updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
                     break;
@@ -4140,6 +4157,15 @@
         }
     }
 
+    private boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
+            NetworkRequest networkRequest) {
+        if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+            return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(callingUid,
+                    networkRequest);
+        }
+        return false;
+    }
+
     private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
         // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
@@ -4163,6 +4189,7 @@
 
     private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
+        NetworkRequest requestToBeReleased = null;
         for (final NetworkRequestInfo nri : nris) {
             mNetworkRequestInfoLogs.log("REGISTER " + nri);
             checkNrisConsistency(nri);
@@ -4177,7 +4204,15 @@
                         }
                     }
                 }
+                if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+                    if (!hasCarrierPrivilegeForNetworkRequest(nri.mUid, req)
+                            && !checkConnectivityRestrictedNetworksPermission(
+                                    nri.mPid, nri.mUid)) {
+                        requestToBeReleased = req;
+                    }
+                }
             }
+
             // If this NRI has a satisfier already, it is replacing an older request that
             // has been removed. Track it.
             final NetworkRequest activeRequest = nri.getActiveRequest();
@@ -4187,6 +4222,11 @@
             }
         }
 
+        if (requestToBeReleased != null) {
+            releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
+            return;
+        }
+
         if (mFlags.noRematchAllRequestsOnRegister()) {
             rematchNetworksAndRequests(nris);
         } else {
@@ -5026,6 +5066,11 @@
                             /* callOnUnavailable */ false);
                     break;
                 }
+                case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
+                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
+                            /* callOnUnavailable */ true);
+                    break;
+                }
                 case EVENT_SET_ACCEPT_UNVALIDATED: {
                     Network network = (Network) msg.obj;
                     handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -6177,6 +6222,9 @@
         if (nc.isPrivateDnsBroken()) {
             throw new IllegalArgumentException("Can't request broken private DNS");
         }
+        if (nc.hasAccessUids()) {
+            throw new IllegalArgumentException("Can't request access UIDs");
+        }
     }
 
     // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@@ -6330,12 +6378,24 @@
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag) {
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
-            enforceConnectivityRestrictedNetworksPermission();
+            if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+                enforceConnectivityRestrictedNetworksPermission();
+            }
         } else {
             enforceChangePermission(callingPackageName, callingAttributionTag);
         }
     }
 
+    private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
+        if (checkAnyPermissionOf(callerPid, callerUid,
+                android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
+                || checkAnyPermissionOf(callerPid, callerUid,
+                android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public boolean requestBandwidthUpdate(Network network) {
         enforceAccessPermission();
@@ -6399,9 +6459,9 @@
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
-        ensureValidNetworkSpecifier(networkCapabilities);
         restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
                 callingUid, callingPackageName);
+        ensureValid(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -6518,6 +6578,13 @@
                 EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
     }
 
+    private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
+        ensureNetworkRequestHasType(networkRequest);
+        mHandler.sendMessage(mHandler.obtainMessage(
+                EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
+                networkRequest));
+    }
+
     private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
         if (mNetworkProviderInfos.containsKey(npi.messenger)) {
             // Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -6915,28 +6982,18 @@
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
             int uid) {
-        if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-            // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
-            // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
-            // sees capabilities that may be malicious, which might prevent mistakes in the future.
-            networkCapabilities = new NetworkCapabilities(networkCapabilities);
-            networkCapabilities.restrictCapabilitiesForTestNetwork(uid);
-        }
 
-        LinkProperties lp = new LinkProperties(linkProperties);
-
-        final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+        // At this point the capabilities/properties are untrusted and unverified, e.g. checks that
+        // the capabilities' access UID comply with security limitations. They will be sanitized
+        // as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
+        // because some of the checks must happen on the handler thread.
         final NetworkAgentInfo nai = new NetworkAgentInfo(na,
-                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
+                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo),
+                linkProperties, networkCapabilities,
                 currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
                 this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
                 mQosCallbackTracker, mDeps);
 
-        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
-        processCapabilitiesFromAgent(nai, nc);
-        nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
-        processLinkPropertiesFromAgent(nai, nai.linkProperties);
-
         final String extraInfo = networkInfo.getExtraInfo();
         final String name = TextUtils.isEmpty(extraInfo)
                 ? nai.networkCapabilities.getSsid() : extraInfo;
@@ -6951,8 +7008,20 @@
     }
 
     private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+        if (VDBG) log("Network Monitor created for " +  nai);
+        // nai.nc and nai.lp are the same object that was passed by the network agent if the agent
+        // lives in the same process as this code (e.g. wifi), so make sure this code doesn't
+        // mutate their object
+        final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+        final LinkProperties lp = new LinkProperties(nai.linkProperties);
+        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
+        processCapabilitiesFromAgent(nai, nc);
+        nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
+        processLinkPropertiesFromAgent(nai, lp);
+        nai.linkProperties = lp;
+
         nai.onNetworkMonitorCreated(networkMonitor);
-        if (VDBG) log("Got NetworkAgent Messenger");
+
         mNetworkAgentInfos.add(nai);
         synchronized (mNetworkForNetId) {
             mNetworkForNetId.put(nai.network.getNetId(), nai);
@@ -6963,6 +7032,7 @@
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
+
         nai.notifyRegistered();
         NetworkInfo networkInfo = nai.networkInfo;
         updateNetworkInfo(nai, networkInfo);
@@ -7410,9 +7480,11 @@
      * Stores into |nai| any data coming from the agent that might also be written to the network's
      * NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
      * agent is not lost when updateCapabilities is called.
-     * This method should never alter the agent's NetworkCapabilities, only store data in |nai|.
      */
     private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
+        if (nc.hasConnectivityManagedCapability()) {
+            Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
+        }
         // Note: resetting the owner UID before storing the agent capabilities in NAI means that if
         // the agent attempts to change the owner UID, then nai.declaredCapabilities will not
         // actually be the same as the capabilities sent by the agent. Still, it is safer to reset
@@ -7423,6 +7495,7 @@
             nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
         }
         nai.declaredCapabilities = new NetworkCapabilities(nc);
+        NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid);
     }
 
     /** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -7687,6 +7760,17 @@
         return stableRanges;
     }
 
+    private static UidRangeParcel[] intsToUidRangeStableParcels(
+            final @NonNull ArraySet<Integer> uids) {
+        final UidRangeParcel[] stableRanges = new UidRangeParcel[uids.size()];
+        int index = 0;
+        for (int uid : uids) {
+            stableRanges[index] = new UidRangeParcel(uid, uid);
+            index++;
+        }
+        return stableRanges;
+    }
+
     private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
         final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
         for (int i = 0; i < ranges.length; i++) {
@@ -7757,8 +7841,14 @@
         }
     }
 
-    private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
-            NetworkCapabilities newNc) {
+    private void updateUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+            @Nullable NetworkCapabilities newNc) {
+        updateVpnUids(nai, prevNc, newNc);
+        updateAccessUids(nai, prevNc, newNc);
+    }
+
+    private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+            @Nullable NetworkCapabilities newNc) {
         Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
         Set<UidRange> newRanges = null == newNc ? null : newNc.getUidRanges();
         if (null == prevRanges) prevRanges = new ArraySet<>();
@@ -7817,6 +7907,46 @@
         }
     }
 
+    private void updateAccessUids(@NonNull NetworkAgentInfo nai,
+            @Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
+        // In almost all cases both NC code for empty access UIDs. return as fast as possible.
+        final boolean prevEmpty = null == prevNc || prevNc.getAccessUidsNoCopy().isEmpty();
+        final boolean newEmpty = null == newNc || newNc.getAccessUidsNoCopy().isEmpty();
+        if (prevEmpty && newEmpty) return;
+
+        final ArraySet<Integer> prevUids =
+                null == prevNc ? new ArraySet<>() : prevNc.getAccessUidsNoCopy();
+        final ArraySet<Integer> newUids =
+                null == newNc ? new ArraySet<>() : newNc.getAccessUidsNoCopy();
+
+        if (prevUids.equals(newUids)) return;
+
+        // This implementation is very simple and vastly faster for sets of Integers than
+        // CompareOrUpdateResult, which is tuned for sets that need to be compared based on
+        // a key computed from the value and has storage for that.
+        final ArraySet<Integer> toRemove = new ArraySet<>(prevUids);
+        final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
+        toRemove.removeAll(newUids);
+        toAdd.removeAll(prevUids);
+
+        try {
+            if (!toAdd.isEmpty()) {
+                mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
+                        nai.network.netId,
+                        intsToUidRangeStableParcels(toAdd),
+                        PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+            }
+            if (!toRemove.isEmpty()) {
+                mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+                        nai.network.netId,
+                        intsToUidRangeStableParcels(toRemove),
+                        PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+            }
+        } catch (RemoteException e) {
+            // Netd died. This usually causes a runtime restart anyway.
+        }
+    }
+
     public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
         ensureRunningOnConnectivityServiceThread();
 
@@ -9783,7 +9913,7 @@
                 android.Manifest.permission.NETWORK_STACK);
         final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
         if (!nc.hasTransport(TRANSPORT_TEST)) {
-            throw new SecurityException("Data Stall simluation is only possible for test networks");
+            throw new SecurityException("Data Stall simulation is only possible for test networks");
         }
 
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
@@ -10709,11 +10839,11 @@
 
         try {
             if (add) {
-                mNetd.bandwidthAddNiceApp(uid);
+                mBpfNetMaps.addNiceApp(uid);
             } else {
-                mNetd.bandwidthRemoveNiceApp(uid);
+                mBpfNetMaps.removeNiceApp(uid);
             }
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10724,11 +10854,11 @@
 
         try {
             if (add) {
-                mNetd.bandwidthAddNaughtyApp(uid);
+                mBpfNetMaps.addNaughtyApp(uid);
             } else {
-                mNetd.bandwidthRemoveNaughtyApp(uid);
+                mBpfNetMaps.removeNaughtyApp(uid);
             }
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10738,9 +10868,9 @@
         enforceNetworkStackOrSettingsPermission();
 
         try {
-            mNetd.firewallSetUidRule(chain, uid,
+            mBpfNetMaps.setUidRule(chain, uid,
                     allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10750,8 +10880,8 @@
         enforceNetworkStackOrSettingsPermission();
 
         try {
-            mNetd.firewallEnableChildChain(chain, enable);
-        } catch (RemoteException | ServiceSpecificException e) {
+            mBpfNetMaps.setChildChain(chain, enable);
+        } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10763,22 +10893,22 @@
         try {
             switch (chain) {
                 case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
-                    mNetd.firewallReplaceUidChain("fw_dozable", true /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_dozable", true /* isAllowList */, uids);
                     break;
                 case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
-                    mNetd.firewallReplaceUidChain("fw_standby", false /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_standby", false /* isAllowList */, uids);
                     break;
                 case ConnectivityManager.FIREWALL_CHAIN_POWERSAVE:
-                    mNetd.firewallReplaceUidChain("fw_powersave", true /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_powersave", true /* isAllowList */, uids);
                     break;
                 case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
-                    mNetd.firewallReplaceUidChain("fw_restricted", true /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_restricted", true /* isAllowList */, uids);
                     break;
                 default:
                     throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
                             + chain);
             }
-        } catch (RemoteException | ServiceSpecificException e) {
+        } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10787,8 +10917,8 @@
     public void swapActiveStatsMap() {
         enforceNetworkStackOrSettingsPermission();
         try {
-            mNetd.trafficSwapActiveStatsMap();
-        } catch (RemoteException | ServiceSpecificException e) {
+            mBpfNetMaps.swapActiveStatsMap();
+        } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
new file mode 100644
index 0000000..c5107ad
--- /dev/null
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -0,0 +1,316 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Tracks the uid of the carrier privileged app that provides the carrier config.
+ * Authenticates if the caller has same uid as
+ * carrier privileged app that provides the carrier config
+ * @hide
+ */
+public class CarrierPrivilegeAuthenticator extends BroadcastReceiver {
+    private static final String TAG = CarrierPrivilegeAuthenticator.class.getSimpleName();
+    private static final boolean DBG = true;
+
+    // The context is for the current user (system server)
+    private final Context mContext;
+    private final TelephonyManagerShim mTelephonyManagerShim;
+    private final TelephonyManager mTelephonyManager;
+    @GuardedBy("mLock")
+    private int[] mCarrierServiceUid;
+    @GuardedBy("mLock")
+    private int mModemCount = 0;
+    private final Object mLock = new Object();
+    private final HandlerThread mThread;
+    private final Handler mHandler;
+    @NonNull
+    private final List<CarrierPrivilegesListenerShim> mCarrierPrivilegesChangedListeners =
+            new ArrayList<>();
+
+    public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+            @NonNull final TelephonyManager t,
+            @NonNull final TelephonyManagerShimImpl telephonyManagerShim) {
+        mContext = c;
+        mTelephonyManager = t;
+        mTelephonyManagerShim = telephonyManagerShim;
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper()) {};
+        synchronized (mLock) {
+            mModemCount = mTelephonyManager.getActiveModemCount();
+            registerForCarrierChanges();
+            updateCarrierServiceUid();
+        }
+    }
+
+    public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+            @NonNull final TelephonyManager t) {
+        mContext = c;
+        mTelephonyManager = t;
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
+            mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
+        } else {
+            mTelephonyManagerShim = null;
+        }
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper()) {};
+        synchronized (mLock) {
+            mModemCount = mTelephonyManager.getActiveModemCount();
+            registerForCarrierChanges();
+            updateCarrierServiceUid();
+        }
+    }
+
+    /**
+     * An adapter {@link Executor} that posts all executed tasks onto the given
+     * {@link Handler}.
+     *
+     * TODO : migrate to the version in frameworks/libs/net when it's ready
+     *
+     * @hide
+     */
+    public class HandlerExecutor implements Executor {
+        private final Handler mHandler;
+        public HandlerExecutor(@NonNull Handler handler) {
+            mHandler = handler;
+        }
+        @Override
+        public void execute(Runnable command) {
+            if (!mHandler.post(command)) {
+                throw new RejectedExecutionException(mHandler + " is shutting down");
+            }
+        }
+    }
+
+    /**
+     * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
+     *
+     * <p>The broadcast receiver is registered with mHandler
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        switch (intent.getAction()) {
+            case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+                handleActionMultiSimConfigChanged(context, intent);
+                break;
+            default:
+                Log.d(TAG, "Unknown intent received with action: " + intent.getAction());
+        }
+    }
+
+    private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
+        unregisterCarrierPrivilegesListeners();
+        synchronized (mLock) {
+            mModemCount = mTelephonyManager.getActiveModemCount();
+        }
+        registerCarrierPrivilegesListeners();
+        updateCarrierServiceUid();
+    }
+
+    private void registerForCarrierChanges() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+        mContext.registerReceiver(this, filter, null, mHandler);
+        registerCarrierPrivilegesListeners();
+    }
+
+    private void registerCarrierPrivilegesListeners() {
+        final HandlerExecutor executor = new HandlerExecutor(mHandler);
+        int modemCount;
+        synchronized (mLock) {
+            modemCount = mModemCount;
+        }
+        try {
+            for (int i = 0; i < modemCount; i++) {
+                CarrierPrivilegesListenerShim carrierPrivilegesListener =
+                        new CarrierPrivilegesListenerShim() {
+                            @Override
+                            public void onCarrierPrivilegesChanged(
+                                    @NonNull List<String> privilegedPackageNames,
+                                    @NonNull int[] privilegedUids) {
+                                // Re-trigger the synchronous check (which is also very cheap due
+                                // to caching in CarrierPrivilegesTracker). This allows consistency
+                                // with the onSubscriptionsChangedListener and broadcasts.
+                                updateCarrierServiceUid();
+                            }
+                        };
+                addCarrierPrivilegesListener(i, executor, carrierPrivilegesListener);
+                mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener);
+            }
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Encountered exception registering carrier privileges listeners", e);
+        }
+    }
+
+    private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
+            CarrierPrivilegesListenerShim listener) {
+        if (mTelephonyManagerShim  == null) {
+            return;
+        }
+        try {
+            mTelephonyManagerShim.addCarrierPrivilegesListener(
+                    logicalSlotIndex, executor, listener);
+        } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+            Log.e(TAG, "addCarrierPrivilegesListener API is not available");
+        }
+    }
+
+    private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
+        if (mTelephonyManagerShim  == null) {
+            return;
+        }
+        try {
+            mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
+        } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+            Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
+        }
+    }
+
+    private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
+        if (mTelephonyManagerShim  == null) {
+            return null;
+        }
+        try {
+            return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
+                    logicalSlotIndex);
+        } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+            Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
+        }
+        return null;
+    }
+
+    private void unregisterCarrierPrivilegesListeners() {
+        for (CarrierPrivilegesListenerShim carrierPrivilegesListener :
+                mCarrierPrivilegesChangedListeners) {
+            removeCarrierPrivilegesListener(carrierPrivilegesListener);
+        }
+        mCarrierPrivilegesChangedListeners.clear();
+    }
+
+    /**
+     * Check if network request is allowed based upon carrrier service package.
+     *
+     * Network request for {@link NET_CAPABILITY_CBS} is allowed if the caller has
+     * carrier privilege and provides the carrier config. This function checks if caller
+     * has the same and returns true if it has else false.
+     *
+     * @param callingUid user identifier that uniquely identifies the caller.
+     * @param networkRequest the network request for which the carrier privilege is checked.
+     * @return true if caller has carrier privilege and provides the carrier config else false.
+     */
+    public boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
+            NetworkRequest networkRequest) {
+        if (callingUid != Process.INVALID_UID) {
+            final int subId = getSubIdFromNetworkSpecifier(
+                    networkRequest.getNetworkSpecifier());
+            return callingUid == getCarrierServiceUidForSubId(subId);
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    void updateCarrierServiceUid() {
+        synchronized (mLock) {
+            mCarrierServiceUid = new int[mModemCount];
+            for (int i = 0; i < mModemCount; i++) {
+                mCarrierServiceUid[i] = getCarrierServicePackageUidForSlot(i);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    int getCarrierServiceUidForSubId(int subId) {
+        final int slotId = getSlotIndex(subId);
+        synchronized (mLock) {
+            if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mModemCount) {
+                return mCarrierServiceUid[slotId];
+            }
+        }
+        return Process.INVALID_UID;
+    }
+
+    @VisibleForTesting
+    protected int getSlotIndex(int subId) {
+        return SubscriptionManager.getSlotIndex(subId);
+    }
+
+    @VisibleForTesting
+    int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
+        if (specifier instanceof TelephonyNetworkSpecifier) {
+            return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+        }
+        return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+    }
+
+    @VisibleForTesting
+    int getUidForPackage(String pkgName) {
+        if (pkgName == null) {
+            return Process.INVALID_UID;
+        }
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            if (pm != null) {
+                ApplicationInfo applicationInfo = pm.getApplicationInfo(pkgName, 0);
+                if (applicationInfo != null) {
+                    return applicationInfo.uid;
+                }
+            }
+        } catch (PackageManager.NameNotFoundException exception) {
+            // Didn't find package. Try other users
+            Log.i(TAG, "Unable to find uid for package " + pkgName);
+        }
+        return Process.INVALID_UID;
+    }
+
+    @VisibleForTesting
+    int getCarrierServicePackageUidForSlot(int slotId) {
+        return getUidForPackage(getCarrierServicePackageNameForLogicalSlot(slotId));
+    }
+}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 1a7248a..b9e2a5f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,7 +17,9 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -52,6 +54,7 @@
 import android.os.SystemClock;
 import android.telephony.data.EpsBearerQosSessionAttributes;
 import android.telephony.data.NrQosSessionAttributes;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -1188,6 +1191,38 @@
         return mConnectivityReport;
     }
 
+    /**
+     * Make sure the NC from network agents don't contain stuff they shouldn't.
+     *
+     * @param nc the capabilities to sanitize
+     * @param creatorUid the UID of the process creating this network agent
+     */
+    public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+            final int creatorUid) {
+        if (nc.hasTransport(TRANSPORT_TEST)) {
+            nc.restrictCapabilitiesForTestNetwork(creatorUid);
+        }
+        if (!areAccessUidsAcceptableFromNetworkAgent(nc)) {
+            nc.setAccessUids(new ArraySet<>());
+        }
+    }
+
+    private static boolean areAccessUidsAcceptableFromNetworkAgent(
+            @NonNull final NetworkCapabilities nc) {
+        // NCs without access UIDs are fine.
+        if (!nc.hasAccessUids()) return true;
+
+        // On a non-restricted network, access UIDs make no sense
+        if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
+
+        // If this network has TRANSPORT_TEST, then the caller can do whatever they want to
+        // access UIDs
+        if (nc.hasTransport(TRANSPORT_TEST)) return true;
+
+        // TODO : accept more supported cases
+        return false;
+    }
+
     // TODO: Print shorter members first and only print the boolean variable which value is true
     // to improve readability.
     public String toString() {
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 439db89..c9c1776 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -68,6 +68,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -93,6 +94,7 @@
     private final INetd mNetd;
     private final Dependencies mDeps;
     private final Context mContext;
+    private final BpfNetMaps mBpfNetMaps;
 
     @GuardedBy("this")
     private final Set<UserHandle> mUsers = new HashSet<>();
@@ -184,12 +186,14 @@
         }
     }
 
-    public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
-        this(context, netd, new Dependencies());
+    public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
+            @NonNull final BpfNetMaps bpfNetMaps) {
+        this(context, netd, bpfNetMaps, new Dependencies());
     }
 
     @VisibleForTesting
     PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
+            @NonNull final BpfNetMaps bpfNetMaps,
             @NonNull final Dependencies deps) {
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -197,6 +201,7 @@
         mNetd = netd;
         mDeps = deps;
         mContext = context;
+        mBpfNetMaps = bpfNetMaps;
     }
 
     private int getPackageNetdNetworkPermission(@NonNull final PackageInfo app) {
@@ -803,9 +808,9 @@
         }
         try {
             if (add) {
-                mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+                mBpfNetMaps.addUidInterfaceRules(iface, toIntArray(uids));
             } else {
-                mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+                mBpfNetMaps.removeUidInterfaceRules(toIntArray(uids));
             }
         } catch (ServiceSpecificException e) {
             // Silently ignore exception when device does not support eBPF, otherwise just log
@@ -813,8 +818,6 @@
             if (e.errorCode != OsConstants.EOPNOTSUPP) {
                 loge("Exception when updating permissions: ", e);
             }
-        } catch (RemoteException e) {
-            loge("Exception when updating permissions: ", e);
         }
     }
 
@@ -878,26 +881,27 @@
         try {
             // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
             if (allPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(
+                mBpfNetMaps.setNetPermForUids(
                         PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
                         toIntArray(allPermissionAppIds));
             }
             if (internetPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_INTERNET,
+                mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET,
                         toIntArray(internetPermissionAppIds));
             }
             if (updateStatsPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
+                mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
                         toIntArray(updateStatsPermissionAppIds));
             }
             if (noPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_NONE, toIntArray(noPermissionAppIds));
+                mBpfNetMaps.setNetPermForUids(PERMISSION_NONE,
+                        toIntArray(noPermissionAppIds));
             }
             if (uninstalledAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_UNINSTALLED,
+                mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED,
                         toIntArray(uninstalledAppIds));
             }
-        } catch (RemoteException e) {
+        } catch (ServiceSpecificException e) {
             Log.e(TAG, "Pass appId list of special permission failed." + e);
         }
     }
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index bea00a9..742044b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -308,6 +308,48 @@
         }
     }
 
+    @Test @IgnoreUpTo(SC_V2)
+    public void testSetAccessUids() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        assertThrows(NullPointerException.class, () -> nc.setAccessUids(null));
+        assertFalse(nc.hasAccessUids());
+        assertFalse(nc.isAccessUid(0));
+        assertFalse(nc.isAccessUid(1000));
+        assertEquals(0, nc.getAccessUids().size());
+        nc.setAccessUids(new ArraySet<>());
+        assertFalse(nc.hasAccessUids());
+        assertFalse(nc.isAccessUid(0));
+        assertFalse(nc.isAccessUid(1000));
+        assertEquals(0, nc.getAccessUids().size());
+
+        final ArraySet<Integer> uids = new ArraySet<>();
+        uids.add(200);
+        uids.add(250);
+        uids.add(-1);
+        uids.add(Integer.MAX_VALUE);
+        nc.setAccessUids(uids);
+        assertNotEquals(nc, new NetworkCapabilities());
+        assertTrue(nc.hasAccessUids());
+
+        final List<Integer> includedList = List.of(-2, 0, 199, 700, 901, 1000, Integer.MIN_VALUE);
+        final List<Integer> excludedList = List.of(-1, 200, 250, Integer.MAX_VALUE);
+        for (final int uid : includedList) {
+            assertFalse(nc.isAccessUid(uid));
+        }
+        for (final int uid : excludedList) {
+            assertTrue(nc.isAccessUid(uid));
+        }
+
+        final Set<Integer> outUids = nc.getAccessUids();
+        assertEquals(4, outUids.size());
+        for (final int uid : includedList) {
+            assertFalse(outUids.contains(uid));
+        }
+        for (final int uid : excludedList) {
+            assertTrue(outUids.contains(uid));
+        }
+    }
+
     @Test
     public void testParcelNetworkCapabilities() {
         final Set<Range<Integer>> uids = new ArraySet<>();
@@ -318,6 +360,10 @@
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
         if (isAtLeastS()) {
+            final ArraySet<Integer> accessUids = new ArraySet<>();
+            accessUids.add(4);
+            accessUids.add(9);
+            netCap.setAccessUids(accessUids);
             netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
             netCap.setUids(uids);
         }
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index ef5dc77..344482b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -23,7 +23,6 @@
 import android.net.INetworkAgentRegistry
 import android.net.InetAddresses
 import android.net.IpPrefix
-import android.net.KeepalivePacketData
 import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.NattKeepalivePacketData
@@ -42,6 +41,7 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkInfo
@@ -53,7 +53,6 @@
 import android.net.QosCallback
 import android.net.QosCallbackException
 import android.net.QosCallback.QosCallbackRegistrationException
-import android.net.QosFilter
 import android.net.QosSession
 import android.net.QosSessionAttributes
 import android.net.QosSocketInfo
@@ -67,7 +66,6 @@
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
-import android.os.Looper
 import android.os.Message
 import android.os.SystemClock
 import android.telephony.TelephonyManager
@@ -82,6 +80,8 @@
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Losing
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.TestableNetworkAgent
@@ -100,7 +100,6 @@
 import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
 import com.android.testutils.TestableNetworkCallback
 import org.junit.After
-import org.junit.Assert.assertArrayEquals
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Test
@@ -462,6 +461,36 @@
         }
     }
 
+    private fun ncWithAccessUids(vararg uids: Int) = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .setAccessUids(uids.toSet()).build()
+
+    @Test
+    fun testRejectedUpdates() {
+        val callback = TestableNetworkCallback()
+        // will be cleaned up in tearDown
+        registerNetworkCallback(makeTestNetworkRequest(), callback)
+        val agent = createNetworkAgent(initialNc = ncWithAccessUids(200))
+        agent.register()
+        agent.markConnected()
+
+        // Make sure the UIDs have been ignored.
+        callback.expectCallback<Available>(agent.network!!)
+        callback.expectCapabilitiesThat(agent.network!!) {
+            it.accessUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        callback.expectCallback<LinkPropertiesChanged>(agent.network!!)
+        callback.expectCallback<BlockedStatus>(agent.network!!)
+        callback.expectCapabilitiesThat(agent.network!!) {
+            it.accessUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+        // Make sure that the UIDs are also ignored upon update
+        agent.sendNetworkCapabilities(ncWithAccessUids(200, 300))
+        callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+    }
+
     @Test
     fun testSendScore() {
         // This test will create two networks and check that the one with the stronger
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 4d4e7b9..eb7c2d1 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -53,7 +53,7 @@
         // android_library does not include JNI libs: include NetworkStack dependencies here
         "libnativehelper_compat_libc++",
         "libnetworkstackutilsjni",
-        "libcom_android_connectivity_com_android_net_module_util_jni",
+        "libandroid_net_connectivity_com_android_net_module_util_jni",
     ],
     jarjar_rules: ":connectivity-jarjar-rules",
 }
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 4dc86ff..365c0cf 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 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_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -109,6 +110,9 @@
             case TRANSPORT_WIFI_AWARE:
                 mScore = new NetworkScore.Builder().setLegacyInt(20).build();
                 break;
+            case TRANSPORT_TEST:
+                mScore = new NetworkScore.Builder().build();
+                break;
             case TRANSPORT_VPN:
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
                 // VPNs deduce the SUSPENDED capability from their underlying networks and there
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
new file mode 100644
index 0000000..ac21e77
--- /dev/null
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 com.android.server;
+
+import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
+import static android.net.INetd.FIREWALL_RULE_ALLOW;
+import static android.net.INetd.PERMISSION_INTERNET;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.Mockito.verify;
+
+import android.net.INetd;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public final class BpfNetMapsTest {
+    private static final String TAG = "BpfNetMapsTest";
+    private static final int TEST_UID = 10086;
+    private static final int[] TEST_UIDS = {10002, 10003};
+    private static final String IFNAME = "wlan0";
+    private static final String CHAINNAME = "fw_dozable";
+    private BpfNetMaps mBpfNetMaps;
+
+    @Mock INetd mNetd;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mBpfNetMaps = new BpfNetMaps(mNetd);
+    }
+
+    @Test
+    public void testBpfNetMapsBeforeT() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastT());
+        mBpfNetMaps.addNaughtyApp(TEST_UID);
+        verify(mNetd).bandwidthAddNaughtyApp(TEST_UID);
+        mBpfNetMaps.removeNaughtyApp(TEST_UID);
+        verify(mNetd).bandwidthRemoveNaughtyApp(TEST_UID);
+        mBpfNetMaps.addNiceApp(TEST_UID);
+        verify(mNetd).bandwidthAddNiceApp(TEST_UID);
+        mBpfNetMaps.removeNiceApp(TEST_UID);
+        verify(mNetd).bandwidthRemoveNiceApp(TEST_UID);
+        mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true);
+        verify(mNetd).firewallEnableChildChain(FIREWALL_CHAIN_DOZABLE, true);
+        mBpfNetMaps.replaceUidChain(CHAINNAME, true, TEST_UIDS);
+        verify(mNetd).firewallReplaceUidChain(CHAINNAME, true, TEST_UIDS);
+        mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW);
+        verify(mNetd).firewallSetUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW);
+        mBpfNetMaps.addUidInterfaceRules(IFNAME, TEST_UIDS);
+        verify(mNetd).firewallAddUidInterfaceRules(IFNAME, TEST_UIDS);
+        mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
+        verify(mNetd).firewallRemoveUidInterfaceRules(TEST_UIDS);
+        mBpfNetMaps.swapActiveStatsMap();
+        verify(mNetd).trafficSwapActiveStatsMap();
+        mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+        verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+    }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 652aee9..2985c41 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
 import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
@@ -108,6 +109,7 @@
 import static android.net.NetworkCapabilities.REDACT_NONE;
 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_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -1490,6 +1492,10 @@
                 r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
     }
 
+    private UidRangeParcel[] intToUidRangeStableParcels(final @NonNull Set<Integer> ranges) {
+        return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
+    }
+
     private VpnManagerService makeVpnManagerService() {
         final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
             public int getCallingUid() {
@@ -3813,14 +3819,14 @@
     public void testNoMutableNetworkRequests() throws Exception {
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
-        NetworkRequest request1 = new NetworkRequest.Builder()
+        final NetworkRequest request1 = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_VALIDATED)
                 .build();
-        NetworkRequest request2 = new NetworkRequest.Builder()
+        final NetworkRequest request2 = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
                 .build();
 
-        Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
         assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
         assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
         assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
@@ -3828,6 +3834,36 @@
     }
 
     @Test
+    public void testNoAccessUidsInNetworkRequests() throws Exception {
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+        final NetworkRequest r = new NetworkRequest.Builder().build();
+        final ArraySet<Integer> accessUids = new ArraySet<>();
+        accessUids.add(6);
+        accessUids.add(9);
+        r.networkCapabilities.setAccessUids(accessUids);
+
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final NetworkCallback cb = new NetworkCallback();
+
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertThrows(expected, () -> mCm.requestNetwork(r, cb));
+        assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent));
+        assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler));
+
+        // Make sure that resetting the access UIDs to the empty set will allow calling
+        // requestNetwork and registerNetworkCallback.
+        r.networkCapabilities.setAccessUids(Collections.emptySet());
+        mCm.requestNetwork(r, cb);
+        mCm.unregisterNetworkCallback(cb);
+        mCm.registerNetworkCallback(r, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    @Test
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -5727,6 +5763,22 @@
         }
     }
 
+    /**
+     * Validate the callback flow CBS request without carrier privilege.
+     */
+    @Test
+    public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
+        final NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_CBS).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+
+        mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+        // Now file the test request and expect it.
+        mCm.requestNetwork(nr, networkCallback);
+        networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+        mCm.unregisterNetworkCallback(networkCallback);
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }
@@ -14555,6 +14607,75 @@
                 () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
     }
 
+    @Test
+    public void testAccessUids() throws Exception {
+        final int preferenceOrder =
+                ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(TRANSPORT_TEST)
+                        .build(),
+                cb);
+
+        final ArraySet<Integer> uids = new ArraySet<>();
+        uids.add(200);
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setAccessUids(uids)
+                .build();
+        final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+                new LinkProperties(), nc);
+        agent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(agent);
+
+        final InOrder inOrder = inOrder(mMockNetd);
+        final NativeUidRangeConfig uids200Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+
+        uids.add(300);
+        uids.add(400);
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+
+        uids.remove(200);
+        final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+
+        uids.clear();
+        uids.add(600);
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+
+        uids.clear();
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+    }
+
     /**
      * Validate request counts are counted accurately on setProfileNetworkPreference on set/replace.
      */
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
new file mode 100644
index 0000000..d193aa9
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.telephony.TelephonyManager;
+
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for CarrierPrivilegeAuthenticatorTest.
+ *
+ * Build, install and run with:
+ *  runtest frameworks-net -c com.android.server.connectivity.CarrierPrivilegeAuthenticatorTest
+ */
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+public class CarrierPrivilegeAuthenticatorTest {
+    private static final String PACKAGE_NAME =
+            CarrierPrivilegeAuthenticatorTest.class.getPackage().getName();
+    private static final int TEST_SIM_SLOT_INDEX = 0;
+    private static final int TEST_SUBSCRIPTION_ID_1 = 2;
+    private static final int TEST_SUBSCRIPTION_ID_2 = 3;
+
+    @NonNull private final Context mContext;
+    @NonNull private final TelephonyManager mTelephonyManager;
+    @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
+    @NonNull private final PackageManager mPackageManager;
+    @NonNull private CarrierPrivilegeAuthenticatorChild mCarrierPrivilegeAuthenticator;
+    private final int mCarrierConfigPkgUid = 12345;
+    private final String mTestPkg = "com.android.server.connectivity.test";
+
+    public class CarrierPrivilegeAuthenticatorChild extends CarrierPrivilegeAuthenticator {
+        CarrierPrivilegeAuthenticatorChild(@NonNull final Context c,
+                @NonNull final TelephonyManager t) {
+            super(c, t, mTelephonyManagerShim);
+        }
+        @Override
+        protected int getSlotIndex(int subId) {
+            return subId;
+        }
+    }
+
+    public CarrierPrivilegeAuthenticatorTest() {
+        mContext = mock(Context.class);
+        mTelephonyManager = mock(TelephonyManager.class);
+        mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
+        mPackageManager = mock(PackageManager.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        doReturn(mTestPkg).when(mTelephonyManagerShim)
+                .getCarrierServicePackageNameForLogicalSlot(anyInt());
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = mCarrierConfigPkgUid;
+        doReturn(applicationInfo).when(mPackageManager)
+                .getApplicationInfo(eq(mTestPkg), anyInt());
+        mCarrierPrivilegeAuthenticator =
+                new CarrierPrivilegeAuthenticatorChild(mContext, mTelephonyManager);
+    }
+
+    private IntentFilter getIntentFilter() {
+        final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
+        verify(mContext).registerReceiver(any(), captor.capture(), any(), any());
+        return captor.getValue();
+    }
+
+    private List<TelephonyManagerShim.CarrierPrivilegesListenerShim>
+            getCarrierPrivilegesListeners() {
+        final ArgumentCaptor<TelephonyManagerShim.CarrierPrivilegesListenerShim> captor =
+                ArgumentCaptor.forClass(TelephonyManagerShim.CarrierPrivilegesListenerShim.class);
+        try {
+            verify(mTelephonyManagerShim, atLeastOnce())
+                    .addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
+        } catch (UnsupportedApiLevelException e) {
+
+        }
+        return captor.getAllValues();
+    }
+
+    private Intent buildTestMultiSimConfigBroadcastIntent() {
+        final Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
+        return intent;
+    }
+    @Test
+    public void testConstructor() throws Exception {
+        verify(mContext).registerReceiver(
+                        eq(mCarrierPrivilegeAuthenticator),
+                        any(IntentFilter.class),
+                        any(),
+                        any());
+        final IntentFilter filter = getIntentFilter();
+        assertEquals(1, filter.countActions());
+        assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED));
+
+        verify(mTelephonyManagerShim, times(2))
+                .addCarrierPrivilegesListener(anyInt(), any(), any());
+        verify(mTelephonyManagerShim)
+                .addCarrierPrivilegesListener(eq(0), any(), any());
+        verify(mTelephonyManagerShim)
+                .addCarrierPrivilegesListener(eq(1), any(), any());
+        assertEquals(2, getCarrierPrivilegesListeners().size());
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier(0);
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+                mCarrierConfigPkgUid, networkRequestBuilder.build()));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+    }
+
+    @Test
+    public void testMultiSimConfigChanged() throws Exception {
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+        final List<TelephonyManagerShim.CarrierPrivilegesListenerShim> carrierPrivilegesListeners =
+                getCarrierPrivilegesListeners();
+
+        mCarrierPrivilegeAuthenticator.onReceive(
+                mContext, buildTestMultiSimConfigBroadcastIntent());
+        for (TelephonyManagerShim.CarrierPrivilegesListenerShim carrierPrivilegesListener
+                : carrierPrivilegesListeners) {
+            verify(mTelephonyManagerShim)
+                    .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener));
+        }
+
+        // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other
+        // (2 previously registered during startup, for slots 0 & 1)
+        verify(mTelephonyManagerShim, times(3))
+                .addCarrierPrivilegesListener(anyInt(), any(), any());
+        verify(mTelephonyManagerShim, times(2))
+                .addCarrierPrivilegesListener(eq(0), any(), any());
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier(0);
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+                mCarrierConfigPkgUid, networkRequestBuilder.build()));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+    }
+
+    @Test
+    public void testOnCarrierPrivilegesChanged() throws Exception {
+        final TelephonyManagerShim.CarrierPrivilegesListenerShim listener =
+                getCarrierPrivilegesListeners().get(0);
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier(0);
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = mCarrierConfigPkgUid + 1;
+        doReturn(applicationInfo).when(mPackageManager)
+                .getApplicationInfo(eq(mTestPkg), anyInt());
+        listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
+
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+                mCarrierConfigPkgUid, networkRequestBuilder.build()));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 99ef80b..6590543 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -89,6 +89,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -146,9 +147,11 @@
     @Mock private UserManager mUserManager;
     @Mock private PermissionMonitor.Dependencies mDeps;
     @Mock private SystemConfigManager mSystemConfigManager;
+    @Mock private BpfNetMaps mBpfNetMaps;
 
     private PermissionMonitor mPermissionMonitor;
     private NetdMonitor mNetdMonitor;
+    private BpfMapMonitor mBpfMapMonitor;
 
     @Before
     public void setUp() throws Exception {
@@ -177,8 +180,9 @@
         // by default.
         doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
 
-        mPermissionMonitor = new PermissionMonitor(mContext, mNetdService, mDeps);
+        mPermissionMonitor = new PermissionMonitor(mContext, mNetdService, mBpfNetMaps, mDeps);
         mNetdMonitor = new NetdMonitor(mNetdService);
+        mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
 
         doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
     }
@@ -511,9 +515,37 @@
         assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID12, NETWORK_STACK);
     }
 
+    private class BpfMapMonitor {
+        private final SparseIntArray mAppIdsTrafficPermission = new SparseIntArray();
+        private static final int DOES_NOT_EXIST = -2;
+
+        BpfMapMonitor(BpfNetMaps mockBpfmap) throws Exception {
+            // Add hook to verify and track result of trafficSetNetPerm.
+            doAnswer((InvocationOnMock invocation) -> {
+                final Object[] args = invocation.getArguments();
+                final int permission = (int) args[0];
+                for (final int appId : (int[]) args[1]) {
+                    mAppIdsTrafficPermission.put(appId, permission);
+                }
+                return null;
+            }).when(mockBpfmap).setNetPermForUids(anyInt(), any(int[].class));
+        }
+
+        public void expectTrafficPerm(int permission, int... appIds) {
+            for (final int appId : appIds) {
+                if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
+                    fail("appId " + appId + " does not exist.");
+                }
+                if (mAppIdsTrafficPermission.get(appId) != permission) {
+                    fail("appId " + appId + " has wrong permission: "
+                            + mAppIdsTrafficPermission.get(appId));
+                }
+            }
+        }
+    }
+
     private class NetdMonitor {
         private final SparseIntArray mUidsNetworkPermission = new SparseIntArray();
-        private final SparseIntArray mAppIdsTrafficPermission = new SparseIntArray();
         private static final int DOES_NOT_EXIST = -2;
 
         NetdMonitor(INetd mockNetd) throws Exception {
@@ -545,16 +577,6 @@
                 }
                 return null;
             }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
-
-            // Add hook to verify and track result of trafficSetNetPerm.
-            doAnswer((InvocationOnMock invocation) -> {
-                final Object[] args = invocation.getArguments();
-                final int permission = (int) args[0];
-                for (final int appId : (int[]) args[1]) {
-                    mAppIdsTrafficPermission.put(appId, permission);
-                }
-                return null;
-            }).when(mockNetd).trafficSetNetPermForUids(anyInt(), any(int[].class));
         }
 
         public void expectNetworkPerm(int permission, UserHandle[] users, int... appIds) {
@@ -581,18 +603,6 @@
                 }
             }
         }
-
-        public void expectTrafficPerm(int permission, int... appIds) {
-            for (final int appId : appIds) {
-                if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
-                    fail("appId " + appId + " does not exist.");
-                }
-                if (mAppIdsTrafficPermission.get(appId) != permission) {
-                    fail("appId " + appId + " has wrong permission: "
-                            + mAppIdsTrafficPermission.get(appId));
-                }
-            }
-        }
     }
 
     @Test
@@ -702,30 +712,30 @@
 
         // When VPN is connected, expect a rule to be set up for user app MOCK_UID11
         mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
 
-        reset(mNetdService);
+        reset(mBpfNetMaps);
 
         // When MOCK_UID11 package is uninstalled and reinstalled, expect Netd to be updated
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
         mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_UID11);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
 
-        reset(mNetdService);
+        reset(mBpfNetMaps);
 
         // During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
         // old UID rules then adds the new ones. Expect netd to be updated
         mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
         mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
 
-        reset(mNetdService);
+        reset(mBpfNetMaps);
 
         // When VPN is disconnected, expect rules to be torn down
         mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
         assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
     }
 
@@ -744,13 +754,13 @@
 
         // Newly-installed package should have uid rules added
         addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
 
         // Removed package should have its uid rules removed
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
-        verify(mNetdService, never()).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps, never()).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
     }
 
 
@@ -784,91 +794,91 @@
         // Send the permission information to netd, expect permission updated.
         mPermissionMonitor.sendAppIdsTrafficPermission(netdPermissionsAppIds);
 
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, SYSTEM_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, SYSTEM_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, SYSTEM_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, SYSTEM_APPID2);
 
         // Update permission of MOCK_APPID1, expect new permission show up.
         mPermissionMonitor.sendPackagePermissionsForAppId(MOCK_APPID1, PERMISSION_TRAFFIC_ALL);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Change permissions of SYSTEM_APPID2, expect new permission show up and old permission
         // revoked.
         mPermissionMonitor.sendPackagePermissionsForAppId(SYSTEM_APPID2, PERMISSION_INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, SYSTEM_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, SYSTEM_APPID2);
 
         // Revoke permission from SYSTEM_APPID1, expect no permission stored.
         mPermissionMonitor.sendPackagePermissionsForAppId(SYSTEM_APPID1, PERMISSION_NONE);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, SYSTEM_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, SYSTEM_APPID1);
     }
 
     @Test
     public void testPackageInstall() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         addPackage(MOCK_PACKAGE2, MOCK_UID12, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID2);
     }
 
     @Test
     public void testPackageInstallSharedUid() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Install another package with the same uid and no permissions should not cause the app id
         // to lose permissions.
         addPackage(MOCK_PACKAGE2, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
     }
 
     @Test
     public void testPackageUninstallBasic() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
     }
 
     @Test
     public void testPackageRemoveThenAdd() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
     }
 
     @Test
     public void testPackageUpdate() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
     }
 
     @Test
     public void testPackageUninstallWithMultiplePackages() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Install another package with the same uid but different permissions.
         addPackage(MOCK_PACKAGE2, MOCK_UID11, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_UID11);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_UID11);
 
         // Uninstall MOCK_PACKAGE1 and expect only INTERNET permission left.
         when(mPackageManager.getPackagesForUid(eq(MOCK_UID11)))
                 .thenReturn(new String[]{MOCK_PACKAGE2});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
     }
 
     @Test
@@ -876,7 +886,8 @@
         // Use the real context as this test must ensure the *real* system package holds the
         // necessary permission.
         final Context realContext = InstrumentationRegistry.getContext();
-        final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+        final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService,
+                mBpfNetMaps);
         final PackageManager manager = realContext.getPackageManager();
         final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
                 GET_PERMISSIONS | MATCH_ANY_USER);
@@ -891,8 +902,8 @@
                 .thenReturn(new int[]{ MOCK_UID12 });
 
         mPermissionMonitor.startMonitoring();
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
     }
 
     private BroadcastReceiver expectBroadcastReceiver(String... actions) {
@@ -923,7 +934,7 @@
         buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11, INTERNET,
                 UPDATE_DEVICE_STATS);
         receiver.onReceive(mContext, addedIntent);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Verify receiving PACKAGE_REMOVED intent.
         when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
@@ -931,7 +942,7 @@
                 Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
         removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID11);
         receiver.onReceive(mContext, removedIntent);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
     }
 
     private ContentObserver expectRegisterContentObserver(Uri expectedUri) {
@@ -1070,7 +1081,7 @@
                 .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
         mPermissionMonitor.startMonitoring();
         mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1, MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
 
         final BroadcastReceiver receiver = expectBroadcastReceiver(
                 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1087,8 +1098,8 @@
                 MOCK_APPID1);
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
     }
 
     @Test
@@ -1114,8 +1125,8 @@
                 MOCK_APPID1);
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
     }
 
     @Test
@@ -1128,7 +1139,7 @@
                 .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
         mPermissionMonitor.startMonitoring();
         mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
 
         final BroadcastReceiver receiver = expectBroadcastReceiver(
                 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1140,7 +1151,7 @@
         receiver.onReceive(mContext, externalIntent);
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
     }
 
     @Test
@@ -1155,7 +1166,7 @@
         mPermissionMonitor.startMonitoring();
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
 
         final BroadcastReceiver receiver = expectBroadcastReceiver(
                 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1169,7 +1180,7 @@
         receiver.onReceive(mContext, externalIntent);
         mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 0e2c293..6872f80 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -28,7 +28,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.net.NetworkStatsFactory.kernelToTag;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 45f033c..76c0c38 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -198,6 +198,7 @@
     @Mock
     private LocationPermissionChecker mLocationPermissionChecker;
     private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
+    private @Mock NetworkStatsService.TagStatsDeleter mTagStatsDeleter;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
@@ -358,6 +359,11 @@
             public IBpfMap<U32, U8> getUidCounterSetMap() {
                 return mUidCounterSetMap;
             }
+
+            @Override
+            public NetworkStatsService.TagStatsDeleter getTagStatsDeleter() {
+                return mTagStatsDeleter;
+            }
         };
     }
 
@@ -696,8 +702,10 @@
         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.