bpf: git mv netd bpf/netd (plus update visibility for new path)

Another step towards moving all bpf related stuff into a common directory.

In practice netd_updatable is only actually used for bpf...

The upcoming netbpfload UML test should be checking netd_updatable's
initialization succeeds (useful since that opens a bunch of maps
fetches some programs, and attaches them to a bunch of cgroup hooks).

Test: TreeHugger
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: I65a0fbbf18bf0aa87938df691c0a9ebcca9a084c
diff --git a/bpf/netd/Android.bp b/bpf/netd/Android.bp
new file mode 100644
index 0000000..fe4d999
--- /dev/null
+++ b/bpf/netd/Android.bp
@@ -0,0 +1,96 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_team: "trendy_team_fwk_core_networking",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+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: [
+        "libmodules-utils-build",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnetdutils",
+    ],
+    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",
+        "mts-tethering",
+    ],
+    test_config_template: ":net_native_test_config_template",
+    require_root: true, // required by setrlimitForTest()
+    header_libs: [
+        "bpf_connectivity_headers",
+    ],
+    srcs: [
+        "BpfHandlerTest.cpp",
+        "BpfBaseTest.cpp",
+    ],
+    version_script: ":connectivity_mainline_test_map",
+    stl: "libc++_static",
+    static_libs: [
+        "libbase",
+        "libnetd_updatable",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libnetdutils",
+        "libprocessgroup",
+    ],
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+}
diff --git a/bpf/netd/BpfBaseTest.cpp b/bpf/netd/BpfBaseTest.cpp
new file mode 100644
index 0000000..34dfbb4
--- /dev/null
+++ b/bpf/netd/BpfBaseTest.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <string>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <cutils/qtaguid.h>
+#include <processgroup/processgroup.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <netdutils/NetNativeTestBase.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+#include "netd.h"
+
+using android::base::Result;
+
+namespace android {
+namespace bpf {
+
+// Use the upper limit of uid to avoid conflict with real app uids. We can't use UID_MAX because
+// it's -1, which is INVALID_UID.
+constexpr uid_t TEST_UID = UID_MAX - 1;
+constexpr uint32_t TEST_TAG = 42;
+
+class BpfBasicTest : public NetNativeTestBase {
+  protected:
+    BpfBasicTest() {}
+};
+
+TEST_F(BpfBasicTest, TestCgroupMounted) {
+    std::string cg2_path;
+    ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg2_path));
+    ASSERT_EQ(0, access(cg2_path.c_str(), R_OK));
+    ASSERT_EQ(0, access((cg2_path + "/cgroup.controllers").c_str(), R_OK));
+}
+
+TEST_F(BpfBasicTest, TestTagSocket) {
+    BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
+    ASSERT_TRUE(cookieTagMap.isValid());
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_LE(0, sock);
+    uint64_t cookie = getSocketCookie(sock);
+    ASSERT_NE(NONEXISTENT_COOKIE, cookie);
+    ASSERT_EQ(0, qtaguid_tagSocket(sock, TEST_TAG, TEST_UID));
+    Result<UidTagValue> tagResult = cookieTagMap.readValue(cookie);
+    ASSERT_RESULT_OK(tagResult);
+    ASSERT_EQ(TEST_UID, tagResult.value().uid);
+    ASSERT_EQ(TEST_TAG, tagResult.value().tag);
+    ASSERT_EQ(0, qtaguid_untagSocket(sock));
+    tagResult = cookieTagMap.readValue(cookie);
+    ASSERT_FALSE(tagResult.ok());
+    ASSERT_EQ(ENOENT, tagResult.error().code());
+}
+
+TEST_F(BpfBasicTest, TestCloseSocketWithoutUntag) {
+    BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
+    ASSERT_TRUE(cookieTagMap.isValid());
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_LE(0, sock);
+    uint64_t cookie = getSocketCookie(sock);
+    ASSERT_NE(NONEXISTENT_COOKIE, cookie);
+    ASSERT_EQ(0, qtaguid_tagSocket(sock, TEST_TAG, TEST_UID));
+    Result<UidTagValue> tagResult = cookieTagMap.readValue(cookie);
+    ASSERT_RESULT_OK(tagResult);
+    ASSERT_EQ(TEST_UID, tagResult.value().uid);
+    ASSERT_EQ(TEST_TAG, tagResult.value().tag);
+    ASSERT_EQ(0, close(sock));
+    // Check map periodically until sk destroy handler have done its job.
+    for (int i = 0; i < 1000; i++) {
+        usleep(5000);  // 5ms
+        tagResult = cookieTagMap.readValue(cookie);
+        if (!tagResult.ok()) {
+            ASSERT_EQ(ENOENT, tagResult.error().code());
+            return;
+        }
+    }
+    FAIL() << "socket tag still exist after 5s";
+}
+
+}
+}
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
new file mode 100644
index 0000000..9682545
--- /dev/null
+++ b/bpf/netd/BpfHandler.cpp
@@ -0,0 +1,411 @@
+/**
+ * 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 "BpfHandler.h"
+
+#include <linux/bpf.h>
+#include <inttypes.h>
+
+#include <android-base/unique_fd.h>
+#include <android-modules-utils/sdk_level.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 base::WaitForProperty;
+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.ok()) {
+        return statusFromErrno(errno, fmt::format("Failed to get program from {}", programPath));
+    }
+    if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+        return statusFromErrno(errno, fmt::format("Program {} attach failed", programPath));
+    }
+    return netdutils::status::ok;
+}
+
+static Status checkProgramAccessible(const char* programPath) {
+    unique_fd prog(retrieveProgram(programPath));
+    if (!prog.ok()) {
+        return statusFromErrno(errno, fmt::format("Failed to get program from {}", programPath));
+    }
+    return netdutils::status::ok;
+}
+
+static Status initPrograms(const char* cg2_path) {
+    if (!cg2_path) return Status("cg2_path is NULL");
+
+    // This code was mainlined in T, so this should be trivially satisfied.
+    if (!modules::sdklevel::IsAtLeastT()) return Status("S- platform is unsupported");
+
+    // S requires eBPF support which was only added in 4.9, so this should be satisfied.
+    if (!bpf::isAtLeastKernelVersion(4, 9, 0)) {
+        return Status("kernel version < 4.9.0 is unsupported");
+    }
+
+    // U bumps the kernel requirement up to 4.14
+    if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) {
+        return Status("U+ platform with kernel version < 4.14.0 is unsupported");
+    }
+
+    // U mandates this mount point (though it should also be the case on T)
+    if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
+        return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
+    }
+
+    unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (!cg_fd.ok()) {
+        const int err = errno;
+        ALOGE("Failed to open the cgroup directory: %s", strerror(err));
+        return statusFromErrno(err, "Open the cgroup directory failed");
+    }
+    RETURN_IF_NOT_OK(checkProgramAccessible(XT_BPF_ALLOWLIST_PROG_PATH));
+    RETURN_IF_NOT_OK(checkProgramAccessible(XT_BPF_DENYLIST_PROG_PATH));
+    RETURN_IF_NOT_OK(checkProgramAccessible(XT_BPF_EGRESS_PROG_PATH));
+    RETURN_IF_NOT_OK(checkProgramAccessible(XT_BPF_INGRESS_PROG_PATH));
+    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 (bpf::isAtLeastKernelVersion(4, 14, 0)) {
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_CREATE_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+    }
+
+    if (modules::sdklevel::IsAtLeastV()) {
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_INET4_CONNECT));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT6_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_INET6_CONNECT));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_RECVMSG_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_UDP4_RECVMSG));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_RECVMSG_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_UDP6_RECVMSG));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_SENDMSG_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_UDP4_SENDMSG));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_SENDMSG_PROG_PATH,
+                                    cg_fd, BPF_CGROUP_UDP6_SENDMSG));
+
+        if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+            RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_GETSOCKOPT_PROG_PATH,
+                                        cg_fd, BPF_CGROUP_GETSOCKOPT));
+            RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SETSOCKOPT_PROG_PATH,
+                                        cg_fd, BPF_CGROUP_SETSOCKOPT));
+        }
+
+        if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+            RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
+                                        cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
+        }
+    }
+
+    if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
+        RETURN_IF_NOT_OK(attachProgramToCgroup(
+                "/sys/fs/bpf/netd_readonly/prog_block_bind4_block_port",
+                cg_fd, BPF_CGROUP_INET4_BIND));
+        RETURN_IF_NOT_OK(attachProgramToCgroup(
+                "/sys/fs/bpf/netd_readonly/prog_block_bind6_block_port",
+                cg_fd, BPF_CGROUP_INET6_BIND));
+
+        // This should trivially pass, since we just attached up above,
+        // but BPF_PROG_QUERY is only implemented on 4.19+ kernels.
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_EGRESS) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_INGRESS) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_CREATE) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_BIND) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
+    }
+
+    if (modules::sdklevel::IsAtLeastV()) {
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_RECVMSG) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_RECVMSG) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_SENDMSG) <= 0) abort();
+        if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_SENDMSG) <= 0) abort();
+
+        if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+            if (bpf::queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
+            if (bpf::queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
+        }
+
+        if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+            if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
+        }
+    }
+
+    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) {}
+
+static bool mainlineNetBpfLoadDone() {
+    return !access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+}
+
+// copied with minor changes from waitForProgsLoaded()
+// p/m/C's staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
+static inline void waitForNetProgsLoaded() {
+    // infinite loop until success with 5/10/20/40/60/60/60... delay
+    for (int delay = 5;; delay *= 2) {
+        if (delay > 60) delay = 60;
+        if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))
+            && mainlineNetBpfLoadDone())
+            return;
+        ALOGW("Waited %ds for init.svc.mdnsd_netbpfload=stopped, still waiting...", delay);
+    }
+}
+
+Status BpfHandler::init(const char* cg2_path) {
+    // Note: netd *can* be restarted, so this might get called a second time after boot is complete
+    // at which point we don't need to (and shouldn't) wait for (more importantly start) loading bpf
+
+    if (base::GetProperty("bpf.progs_loaded", "") != "1") {
+        // AOSP platform netd & mainline don't need this (at least prior to U QPR3),
+        // but there could be platform provided (xt_)bpf programs that oem/vendor
+        // modified netd (which calls us during init) depends on...
+        ALOGI("Waiting for platform BPF programs");
+        android::bpf::waitForProgsLoaded();
+    }
+
+    if (!mainlineNetBpfLoadDone()) {
+        // We're on < U QPR3 & it's the first time netd is starting up (unless crashlooping)
+        //
+        // On U QPR3+ netbpfload is guaranteed to run before the platform bpfloader,
+        // so waitForProgsLoaded() implies mainlineNetBpfLoadDone().
+        if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
+            ALOGE("Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.");
+            abort();
+        }
+
+        ALOGI("Waiting for Networking BPF programs");
+        waitForNetProgsLoaded();
+        ALOGI("Networking BPF programs are loaded");
+    }
+
+    ALOGI("BPF programs are loaded");
+
+    RETURN_IF_NOT_OK(initPrograms(cg2_path));
+    RETURN_IF_NOT_OK(initMaps());
+
+    return netdutils::status::ok;
+}
+
+static void mapLockTest(void) {
+    // The maps must be R/W, and as yet unopened (or more specifically not yet lock'ed).
+    const char * const m1 = BPF_NETD_PATH "map_netd_lock_array_test_map";
+    const char * const m2 = BPF_NETD_PATH "map_netd_lock_hash_test_map";
+
+    unique_fd fd0(bpf::mapRetrieveExclusiveRW(m1)); if (!fd0.ok()) abort();  // grabs exclusive lock
+
+    unique_fd fd1(bpf::mapRetrieveExclusiveRW(m2)); if (!fd1.ok()) abort();  // no conflict with fd0
+    unique_fd fd2(bpf::mapRetrieveExclusiveRW(m2)); if ( fd2.ok()) abort();  // busy due to fd1
+    unique_fd fd3(bpf::mapRetrieveRO(m2));          if (!fd3.ok()) abort();  // no lock taken
+    unique_fd fd4(bpf::mapRetrieveRW(m2));          if ( fd4.ok()) abort();  // busy due to fd1
+    fd1.reset();  // releases exclusive lock
+    unique_fd fd5(bpf::mapRetrieveRO(m2));          if (!fd5.ok()) abort();  // no lock taken
+    unique_fd fd6(bpf::mapRetrieveRW(m2));          if (!fd6.ok()) abort();  // now ok
+    unique_fd fd7(bpf::mapRetrieveRO(m2));          if (!fd7.ok()) abort();  // no lock taken
+    unique_fd fd8(bpf::mapRetrieveExclusiveRW(m2)); if ( fd8.ok()) abort();  // busy due to fd6
+
+    fd0.reset();  // releases exclusive lock
+    unique_fd fd9(bpf::mapRetrieveWO(m1));          if (!fd9.ok()) abort();  // grabs exclusive lock
+}
+
+Status BpfHandler::initMaps() {
+    // bpfLock() requires bpfGetFdMapId which is only available on 4.14+ kernels.
+    if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
+        mapLockTest();
+    }
+
+    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(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+    // initialized last so mCookieTagMap.isValid() implies everything else is valid too
+    RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
+    ALOGI("%s successfully", __func__);
+
+    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) {
+    if (!mCookieTagMap.isValid()) return -EPERM;
+
+    if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) return -EPERM;
+
+    // Note that tagging the socket to AID_CLAT is only implemented in JNI ClatCoordinator.
+    // The process is not allowed to tag socket to AID_CLAT via tagSocket() which would cause
+    // process data usage accounting to be bypassed. Tagging AID_CLAT is used for avoiding counting
+    // CLAT traffic data usage twice. See packages/modules/Connectivity/service/jni/
+    // com_android_server_connectivity_ClatCoordinator.cpp
+    if (chargeUid == AID_CLAT) return -EPERM;
+
+    // The socket destroy listener only monitors on the group {INET_TCP, INET_UDP, INET6_TCP,
+    // INET6_UDP}. Tagging listener unsupported socket causes that the tag can't be removed from
+    // tag map automatically. Eventually, the tag map may run out of space because of dead tag
+    // entries. Note that although tagSocket() of net client has already denied the family which
+    // is neither AF_INET nor AF_INET6, the family validation is still added here just in case.
+    // See tagSocket in system/netd/client/NetdClient.cpp and
+    // TrafficController::makeSkDestroyListener in
+    // packages/modules/Connectivity/service/native/TrafficController.cpp
+    // TODO: remove this once the socket destroy listener can detect more types of socket destroy.
+    int socketFamily;
+    socklen_t familyLen = sizeof(socketFamily);
+    if (getsockopt(sockFd, SOL_SOCKET, SO_DOMAIN, &socketFamily, &familyLen)) {
+        ALOGE("Failed to getsockopt SO_DOMAIN: %s, fd: %d", strerror(errno), sockFd);
+        return -errno;
+    }
+    if (socketFamily != AF_INET && socketFamily != AF_INET6) {
+        ALOGE("Unsupported family: %d", socketFamily);
+        return -EAFNOSUPPORT;
+    }
+
+    int socketProto;
+    socklen_t protoLen = sizeof(socketProto);
+    if (getsockopt(sockFd, SOL_SOCKET, SO_PROTOCOL, &socketProto, &protoLen)) {
+        ALOGE("Failed to getsockopt SO_PROTOCOL: %s, fd: %d", strerror(errno), sockFd);
+        return -errno;
+    }
+    if (socketProto != IPPROTO_UDP && socketProto != IPPROTO_TCP) {
+        ALOGE("Unsupported protocol: %d", socketProto);
+        return -EPROTONOSUPPORT;
+    }
+
+    uint64_t sock_cookie = getSocketCookie(sockFd);
+    if (!sock_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. Note though that it isn't really
+    // safe here to iterate over the map since it might be modified by the system server,
+    // which might toggle the live stats map and clean it.
+    const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
+                                              const StatsKey& key,
+                                              const BpfMapRO<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",
+              strerror(configuration.error().code()));
+        return -configuration.error().code();
+    }
+    if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+        ALOGE("unknown configuration value: %d", configuration.value());
+        return -EINVAL;
+    }
+
+    BpfMapRO<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: %s",
+              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 concurrently 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", strerror(res.error().code()));
+        return -res.error().code();
+    }
+    ALOGD("Socket with cookie %" PRIu64 " tagged successfully with tag %" PRIu32 " uid %u "
+              "and real uid %u", sock_cookie, tag, chargeUid, realUid);
+    return 0;
+}
+
+int BpfHandler::untagSocket(int sockFd) {
+    uint64_t sock_cookie = getSocketCookie(sockFd);
+    if (!sock_cookie) return -errno;
+
+    if (!mCookieTagMap.isValid()) return -EPERM;
+    base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
+    if (!res.ok()) {
+        ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
+        return -res.error().code();
+    }
+    ALOGD("Socket with cookie %" PRIu64 " untagged successfully.", sock_cookie);
+    return 0;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/bpf/netd/BpfHandler.h b/bpf/netd/BpfHandler.h
new file mode 100644
index 0000000..9e69efc
--- /dev/null
+++ b/bpf/netd/BpfHandler.h
@@ -0,0 +1,80 @@
+/**
+ * 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 <netdutils/Status.h>
+#include "bpf/BpfMap.h"
+#include "netd.h"
+
+using android::bpf::BpfMap;
+using android::bpf::BpfMapRO;
+
+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;
+    BpfMapRO<StatsKey, StatsValue> mStatsMapA;
+    BpfMapRO<StatsKey, StatsValue> mStatsMapB;
+    BpfMapRO<uint32_t, uint32_t> mConfigurationMap;
+    BpfMapRO<uint32_t, uint8_t> mUidPermissionMap;
+
+    // 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/bpf/netd/BpfHandlerTest.cpp b/bpf/netd/BpfHandlerTest.cpp
new file mode 100644
index 0000000..b38fa16
--- /dev/null
+++ b/bpf/netd/BpfHandlerTest.cpp
@@ -0,0 +1,257 @@
+/*
+ * 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 <private/android_filesystem_config.h>
+#include <sys/socket.h>
+
+#include <gtest/gtest.h>
+
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+#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, uint32_t> mFakeConfigurationMap;
+    BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+
+    void SetUp() {
+        ASSERT_EQ(0, setrlimitForTest());
+
+        mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+        ASSERT_VALID(mFakeCookieTagMap);
+
+        mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+        ASSERT_VALID(mFakeStatsMapA);
+
+        mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_ARRAY, CONFIGURATION_MAP_SIZE);
+        ASSERT_VALID(mFakeConfigurationMap);
+
+        mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+        ASSERT_VALID(mFakeUidPermissionMap);
+
+        mBh.mCookieTagMap = mFakeCookieTagMap;
+        ASSERT_VALID(mBh.mCookieTagMap);
+        mBh.mStatsMapA = mFakeStatsMapA;
+        ASSERT_VALID(mBh.mStatsMapA);
+        mBh.mConfigurationMap = mFakeConfigurationMap;
+        ASSERT_VALID(mBh.mConfigurationMap);
+        // Always write to stats map A by default.
+        static_assert(SELECT_MAP_A == 0, "bpf map arrays are zero-initialized");
+
+        mBh.mUidPermissionMap = mFakeUidPermissionMap;
+        ASSERT_VALID(mBh.mUidPermissionMap);
+    }
+
+    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, TestTagSocketWithUnsupportedFamily) {
+    int packetSocket = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    EXPECT_LE(0, packetSocket);
+    EXPECT_NE(NONEXISTENT_COOKIE, getSocketCookie(packetSocket));
+    EXPECT_EQ(-EAFNOSUPPORT, mBh.tagSocket(packetSocket, TEST_TAG, TEST_UID, TEST_UID));
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithUnsupportedProtocol) {
+    int rawSocket = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_RAW);
+    EXPECT_LE(0, rawSocket);
+    EXPECT_NE(NONEXISTENT_COOKIE, getSocketCookie(rawSocket));
+    EXPECT_EQ(-EPROTONOSUPPORT, mBh.tagSocket(rawSocket, TEST_TAG, TEST_UID, TEST_UID));
+}
+
+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);
+
+    // Tag a socket to AID_CLAT other then realUid.
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_NE(-1, sock);
+    ASSERT_EQ(-EPERM, mBh.tagSocket(sock, TEST_TAG, AID_CLAT, realUid));
+    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/bpf/netd/NetdUpdatable.cpp b/bpf/netd/NetdUpdatable.cpp
new file mode 100644
index 0000000..3b15916
--- /dev/null
+++ b/bpf/netd/NetdUpdatable.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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 "BpfHandler.h"
+
+#include <android-base/logging.h>
+#include <netdutils/Status.h>
+
+#include "NetdUpdatablePublic.h"
+
+static android::net::BpfHandler sBpfHandler;
+
+int libnetd_updatable_init(const char* cg2_path) {
+    android::base::InitLogging(/*argv=*/nullptr);
+    LOG(INFO) << __func__ << ": Initializing";
+
+    android::netdutils::Status ret = sBpfHandler.init(cg2_path);
+    if (!android::netdutils::isOk(ret)) {
+        LOG(ERROR) << __func__ << ": Failed: (" << ret.code() << ") " << ret.msg();
+        abort();
+    }
+    return 0;
+}
+
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+    return sBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
+}
+
+int libnetd_updatable_untagSocket(int sockFd) {
+    return sBpfHandler.untagSocket(sockFd);
+}
diff --git a/bpf/netd/include/NetdUpdatablePublic.h b/bpf/netd/include/NetdUpdatablePublic.h
new file mode 100644
index 0000000..1ca5ea2
--- /dev/null
+++ b/bpf/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/bpf/netd/libnetd_updatable.map.txt b/bpf/netd/libnetd_updatable.map.txt
new file mode 100644
index 0000000..dcb11a1
--- /dev/null
+++ b/bpf/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/bpf/progs/Android.bp b/bpf/progs/Android.bp
index f6717c5..dc1f56d 100644
--- a/bpf/progs/Android.bp
+++ b/bpf/progs/Android.bp
@@ -47,8 +47,8 @@
         "com.android.tethering",
     ],
     visibility: [
+        "//packages/modules/Connectivity/bpf/netd",
         "//packages/modules/Connectivity/DnsResolver",
-        "//packages/modules/Connectivity/netd",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service/native/libs/libclat",
         "//packages/modules/Connectivity/Tethering",