C++程序  |  769行  |  29.46 KB

/*
 * Copyright (C) 2017 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 "TrafficController"
#include <inttypes.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/inet_diag.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#include <linux/unistd.h>
#include <net/if.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <mutex>
#include <unordered_set>
#include <vector>

#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <logwrap/logwrap.h>
#include <netdutils/StatusOr.h>

#include <netdutils/Misc.h>
#include <netdutils/Syscalls.h>
#include "TrafficController.h"
#include "bpf/BpfMap.h"
#include "bpf/bpf_shared.h"

#include "DumpWriter.h"
#include "FirewallController.h"
#include "InterfaceController.h"
#include "NetlinkListener.h"
#include "qtaguid/qtaguid.h"

using namespace android::bpf;

namespace android {
namespace net {

using base::StringPrintf;
using base::unique_fd;
using base::Join;
using netdutils::extract;
using netdutils::Slice;
using netdutils::sSyscalls;
using netdutils::Status;
using netdutils::statusFromErrno;
using netdutils::StatusOr;
using netdutils::status::ok;

constexpr int kSockDiagMsgType = SOCK_DIAG_BY_FAMILY;
constexpr int kSockDiagDoneMsgType = NLMSG_DONE;

StatusOr<std::unique_ptr<NetlinkListenerInterface>> makeSkDestroyListener() {
    const auto& sys = sSyscalls.get();
    ASSIGN_OR_RETURN(auto event, sys.eventfd(0, EFD_CLOEXEC));
    const int domain = AF_NETLINK;
    const int type = SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK;
    const int protocol = NETLINK_INET_DIAG;
    ASSIGN_OR_RETURN(auto sock, sys.socket(domain, type, protocol));

    sockaddr_nl addr = {
        .nl_family = AF_NETLINK,
        .nl_groups = 1 << (SKNLGRP_INET_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET_UDP_DESTROY - 1) |
                     1 << (SKNLGRP_INET6_TCP_DESTROY - 1) | 1 << (SKNLGRP_INET6_UDP_DESTROY - 1)};
    RETURN_IF_NOT_OK(sys.bind(sock, addr));

    const sockaddr_nl kernel = {.nl_family = AF_NETLINK};
    RETURN_IF_NOT_OK(sys.connect(sock, kernel));

    std::unique_ptr<NetlinkListenerInterface> listener =
        std::make_unique<NetlinkListener>(std::move(event), std::move(sock));

    return listener;
}

Status changeOwnerAndMode(const char* path, gid_t group, const char* debugName, bool netdOnly) {
    int ret = chown(path, AID_ROOT, group);
    if (ret != 0) return statusFromErrno(errno, StringPrintf("change %s group failed", debugName));

    if (netdOnly) {
        ret = chmod(path, S_IRWXU);
    } else {
        // Allow both netd and system server to obtain map fd from the path.
        // chmod doesn't grant permission to all processes in that group to
        // read/write the bpf map. They still need correct sepolicy to
        // read/write the map.
        ret = chmod(path, S_IRWXU | S_IRGRP);
    }
    if (ret != 0) return statusFromErrno(errno, StringPrintf("change %s mode failed", debugName));
    return netdutils::status::ok;
}

TrafficController::TrafficController() {
    ebpfSupported = hasBpfSupport();
}

Status initialOwnerMap(BpfMap<uint32_t, uint8_t>& map) {
    // Check and delete all the entries from the map in case it is a runtime
    // restart
    const auto deleteAllEntries = [](const uint32_t& key, BpfMap<uint32_t, uint8_t>& map) {
        Status res = map.deleteValue(key);
        if (!isOk(res) && (res.code() == ENOENT)) {
            ALOGE("Failed to delete data(uid=%u): %s\n", key, strerror(res.code()));
        }
        return netdutils::status::ok;
    };
    // It is safe to delete from this map because nothing will concurrently iterate over it:
    // - Nothing in netd will iterate over it because we're holding mOwnerMatchMutex.
    // - Nothing outside netd iterates over it.
    map.iterate(deleteAllEntries);
    uint32_t mapSettingKey = UID_MAP_ENABLED;
    uint8_t defaultMapState = 0;
    return map.writeValue(mapSettingKey, defaultMapState, BPF_NOEXIST);
}

Status TrafficController::initMaps() {
    std::lock_guard<std::mutex> ownerMapGuard(mOwnerMatchMutex);
    RETURN_IF_NOT_OK(
        mCookieTagMap.getOrCreate(COOKIE_UID_MAP_SIZE, COOKIE_TAG_MAP_PATH, BPF_MAP_TYPE_HASH));

    RETURN_IF_NOT_OK(changeOwnerAndMode(COOKIE_TAG_MAP_PATH, AID_NET_BW_ACCT, "CookieTagMap",
                                        false));

    RETURN_IF_NOT_OK(mUidCounterSetMap.getOrCreate(UID_COUNTERSET_MAP_SIZE, UID_COUNTERSET_MAP_PATH,
                                                   BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(UID_COUNTERSET_MAP_PATH, AID_NET_BW_ACCT,
                                        "UidCounterSetMap", false));

    RETURN_IF_NOT_OK(
        mAppUidStatsMap.getOrCreate(UID_STATS_MAP_SIZE, APP_UID_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(
        changeOwnerAndMode(APP_UID_STATS_MAP_PATH, AID_NET_BW_STATS, "AppUidStatsMap", false));

    RETURN_IF_NOT_OK(
        mUidStatsMap.getOrCreate(UID_STATS_MAP_SIZE, UID_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(UID_STATS_MAP_PATH, AID_NET_BW_STATS, "UidStatsMap",
                                        false));

    RETURN_IF_NOT_OK(
        mTagStatsMap.getOrCreate(TAG_STATS_MAP_SIZE, TAG_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(TAG_STATS_MAP_PATH, AID_NET_BW_STATS, "TagStatsMap",
                                        false));

    RETURN_IF_NOT_OK(mIfaceIndexNameMap.getOrCreate(IFACE_INDEX_NAME_MAP_SIZE,
                                                    IFACE_INDEX_NAME_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(IFACE_INDEX_NAME_MAP_PATH, AID_NET_BW_STATS,
                                        "IfaceIndexNameMap", false));

    RETURN_IF_NOT_OK(
        mDozableUidMap.getOrCreate(UID_OWNER_MAP_SIZE, DOZABLE_UID_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(DOZABLE_UID_MAP_PATH, AID_ROOT, "DozableUidMap", true));
    RETURN_IF_NOT_OK(initialOwnerMap(mDozableUidMap));

    RETURN_IF_NOT_OK(
        mStandbyUidMap.getOrCreate(UID_OWNER_MAP_SIZE, STANDBY_UID_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(STANDBY_UID_MAP_PATH, AID_ROOT, "StandbyUidMap", true));
    RETURN_IF_NOT_OK(initialOwnerMap(mStandbyUidMap));

    RETURN_IF_NOT_OK(mPowerSaveUidMap.getOrCreate(UID_OWNER_MAP_SIZE, POWERSAVE_UID_MAP_PATH,
                                                  BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(POWERSAVE_UID_MAP_PATH, AID_ROOT, "PowerSaveUidMap", true));
    RETURN_IF_NOT_OK(initialOwnerMap(mPowerSaveUidMap));

    RETURN_IF_NOT_OK(
        mIfaceStatsMap.getOrCreate(IFACE_STATS_MAP_SIZE, IFACE_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
    RETURN_IF_NOT_OK(changeOwnerAndMode(IFACE_STATS_MAP_PATH, AID_NET_BW_STATS, "IfaceStatsMap",
                                        false));
    return netdutils::status::ok;
}

Status TrafficController::start() {

    if (!ebpfSupported) {
        return netdutils::status::ok;
    }

    /* When netd restart from a crash without total system reboot, the program
     * is still attached to the cgroup, detach it so the program can be freed
     * and we can load and attach new program into the target cgroup.
     *
     * TODO: Scrape existing socket when run-time restart and clean up the map
     * if the socket no longer exist
     */

    RETURN_IF_NOT_OK(initMaps());

    // Fetch the list of currently-existing interfaces. At this point NetlinkHandler is
    // already running, so it will call addInterface() when any new interface appears.
    std::map<std::string, uint32_t> ifacePairs;
    ASSIGN_OR_RETURN(ifacePairs, InterfaceController::getIfaceList());
    for (const auto& ifacePair:ifacePairs) {
        addInterface(ifacePair.first.c_str(), ifacePair.second);
    }


    auto result = makeSkDestroyListener();
    if (!isOk(result)) {
        ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
    } else {
        mSkDestroyListener = std::move(result.value());
    }
    // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
    const auto rxHandler = [this](const nlmsghdr&, const Slice msg) {
        inet_diag_msg diagmsg = {};
        if (extract(msg, diagmsg) < sizeof(inet_diag_msg)) {
            ALOGE("unrecognized netlink message: %s", toString(msg).c_str());
            return;
        }
        uint64_t sock_cookie = static_cast<uint64_t>(diagmsg.id.idiag_cookie[0]) |
                               (static_cast<uint64_t>(diagmsg.id.idiag_cookie[1]) << 32);

        mCookieTagMap.deleteValue(sock_cookie);
    };
    expectOk(mSkDestroyListener->subscribe(kSockDiagMsgType, rxHandler));

    // In case multiple netlink message comes in as a stream, we need to handle the rxDone message
    // properly.
    const auto rxDoneHandler = [](const nlmsghdr&, const Slice msg) {
        // Ignore NLMSG_DONE  messages
        inet_diag_msg diagmsg = {};
        extract(msg, diagmsg);
    };
    expectOk(mSkDestroyListener->subscribe(kSockDiagDoneMsgType, rxDoneHandler));

    int* status = nullptr;

    std::vector<const char*> prog_args{
        "/system/bin/bpfloader",
    };
    int ret = access(BPF_INGRESS_PROG_PATH, R_OK);
    if (ret != 0 && errno == ENOENT) {
        prog_args.push_back((char*)"-i");
    }
    ret = access(BPF_EGRESS_PROG_PATH, R_OK);
    if (ret != 0 && errno == ENOENT) {
        prog_args.push_back((char*)"-e");
    }
    ret = access(XT_BPF_INGRESS_PROG_PATH, R_OK);
    if (ret != 0 && errno == ENOENT) {
        prog_args.push_back((char*)"-p");
    }
    ret = access(XT_BPF_EGRESS_PROG_PATH, R_OK);
    if (ret != 0 && errno == ENOENT) {
        prog_args.push_back((char*)"-m");
    }

    if (prog_args.size() == 1) {
        // all program are loaded already.
        return netdutils::status::ok;
    }

    prog_args.push_back(nullptr);
    ret = android_fork_execvp(prog_args.size(), (char**)prog_args.data(), status, false, true);
    if (ret) {
        ret = errno;
        ALOGE("failed to execute %s: %s", prog_args[0], strerror(errno));
        return statusFromErrno(ret, "run bpf loader failed");
    }
    return netdutils::status::ok;
}

int TrafficController::tagSocket(int sockFd, uint32_t tag, uid_t uid) {
    if (!ebpfSupported) {
        if (legacy_tagSocket(sockFd, tag, uid)) return -errno;
        return 0;
    }

    uint64_t sock_cookie = getSocketCookie(sockFd);
    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
    UidTag newKey = {.uid = (uint32_t)uid, .tag = tag};

    // 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.
    Status res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
    if (!isOk(res)) {
        ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.code()),
              mCookieTagMap.getMap().get());
    }
    return -res.code();
}

int TrafficController::untagSocket(int sockFd) {
    if (!ebpfSupported) {
        if (legacy_untagSocket(sockFd)) return -errno;
        return 0;
    }
    uint64_t sock_cookie = getSocketCookie(sockFd);

    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
    Status res = mCookieTagMap.deleteValue(sock_cookie);
    if (!isOk(res)) {
        ALOGE("Failed to untag socket: %s\n", strerror(res.code()));
    }
    return -res.code();
}

int TrafficController::setCounterSet(int counterSetNum, uid_t uid) {
    if (counterSetNum < 0 || counterSetNum >= OVERFLOW_COUNTERSET) return -EINVAL;
    Status res;
    if (!ebpfSupported) {
        if (legacy_setCounterSet(counterSetNum, uid)) return -errno;
        return 0;
    }

    // The default counter set for all uid is 0, so deleting the current counterset for that uid
    // will automatically set it to 0.
    if (counterSetNum == 0) {
        Status res = mUidCounterSetMap.deleteValue(uid);
        if (isOk(res) || (!isOk(res) && res.code() == ENOENT)) {
            return 0;
        } else {
            ALOGE("Failed to delete the counterSet: %s\n", strerror(res.code()));
            return -res.code();
        }
    }
    uint8_t tmpCounterSetNum = (uint8_t)counterSetNum;
    res = mUidCounterSetMap.writeValue(uid, tmpCounterSetNum, BPF_ANY);
    if (!isOk(res)) {
        ALOGE("Failed to set the counterSet: %s, fd: %d", strerror(res.code()),
              mUidCounterSetMap.getMap().get());
        return -res.code();
    }
    return 0;
}

int TrafficController::deleteTagData(uint32_t tag, uid_t uid) {

    if (!ebpfSupported) {
        if (legacy_deleteTagData(tag, uid)) return -errno;
        return 0;
    }

    // First we go through the cookieTagMap to delete the target uid tag combination. Or delete all
    // the tags related to the uid if the tag is 0.
    const auto deleteMatchedCookieEntries = [uid, tag](const uint64_t& key, const UidTag& value,
                                                       BpfMap<uint64_t, UidTag>& map) {
        if (value.uid == uid && (value.tag == tag || tag == 0)) {
            Status res = map.deleteValue(key);
            if (isOk(res) || (res.code() == ENOENT)) {
                return netdutils::status::ok;
            }
            ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key, strerror(res.code()));
        }
        // Move forward to next cookie in the map.
        return netdutils::status::ok;
    };
    mCookieTagMap.iterateWithValue(deleteMatchedCookieEntries);
    // Now we go through the Tag stats map and delete the data entry with correct uid and tag
    // combination. Or all tag stats under that uid if the target tag is 0.
    const auto deleteMatchedUidTagEntries = [uid, tag](const StatsKey& key,
                                                       BpfMap<StatsKey, StatsValue>& map) {
        if (key.uid == uid && (key.tag == tag || tag == 0)) {
            Status res = map.deleteValue(key);
            if (isOk(res) || (res.code() == ENOENT)) {
                //Entry is deleted, use the current key to get a new nextKey;
                return netdutils::status::ok;
            }
            ALOGE("Failed to delete data(uid=%u, tag=%u): %s\n", key.uid, key.tag,
                  strerror(res.code()));
        }
        return netdutils::status::ok;
    };
    mTagStatsMap.iterate(deleteMatchedUidTagEntries);
    // If the tag is not zero, we already deleted all the data entry required. If tag is 0, we also
    // need to delete the stats stored in uidStatsMap and counterSet map.
    if (tag != 0) return 0;

    Status res = mUidCounterSetMap.deleteValue(uid);
    if (!isOk(res) && res.code() != ENOENT) {
        ALOGE("Failed to delete counterSet data(uid=%u, tag=%u): %s\n", uid, tag,
              strerror(res.code()));
    }
    mUidStatsMap.iterate(deleteMatchedUidTagEntries);

    auto deleteAppUidStatsEntry = [uid](const uint32_t& key, BpfMap<uint32_t, StatsValue>& map) {
        if (key == uid) {
            Status res = map.deleteValue(key);
            if (isOk(res) || (res.code() == ENOENT)) {
                return netdutils::status::ok;
            }
            ALOGE("Failed to delete data(uid=%u): %s", key, strerror(res.code()));
        }
        return netdutils::status::ok;
    };
    mAppUidStatsMap.iterate(deleteAppUidStatsEntry);
    return 0;
}

int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
    if (!ebpfSupported) return 0;

    IfaceValue iface;
    if (ifaceIndex == 0) {
        ALOGE("Unknown interface %s(%d)", name, ifaceIndex);
        return -1;
    }

    strlcpy(iface.name, name, sizeof(IfaceValue));
    Status res = mIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY);
    if (!isOk(res)) {
        ALOGE("Failed to add iface %s(%d): %s", name, ifaceIndex, strerror(res.code()));
        return -res.code();
    }
    return 0;
}

Status TrafficController::updateOwnerMapEntry(BpfMap<uint32_t, uint8_t>& map, uid_t uid,
                                              FirewallRule rule, FirewallType type) {
    if (uid == UID_MAP_ENABLED) {
        return statusFromErrno(-EINVAL, "This uid is reserved for map state");
    }

    if ((rule == ALLOW && type == WHITELIST) || (rule == DENY && type == BLACKLIST)) {
        uint8_t flag = (type == WHITELIST) ? BPF_PASS : BPF_DROP;
        RETURN_IF_NOT_OK(map.writeValue(uid, flag, BPF_ANY));
    } else if ((rule == ALLOW && type == BLACKLIST) || (rule == DENY && type == WHITELIST)) {
        RETURN_IF_NOT_OK(map.deleteValue(uid));
    } else {
        //Cannot happen.
        return statusFromErrno(-EINVAL, "");
    }
    return netdutils::status::ok;
}

int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallRule rule,
                                          FirewallType type) {
    std::lock_guard<std::mutex> guard(mOwnerMatchMutex);
    if (!ebpfSupported) {
        ALOGE("bpf is not set up, should use iptables rule");
        return -ENOSYS;
    }
    Status res;
    switch (chain) {
        case DOZABLE:
            res = updateOwnerMapEntry(mDozableUidMap, uid, rule, type);
            break;
        case STANDBY:
            res = updateOwnerMapEntry(mStandbyUidMap, uid, rule, type);
            break;
        case POWERSAVE:
            res = updateOwnerMapEntry(mPowerSaveUidMap, uid, rule, type);
            break;
        case NONE:
        default:
            return -EINVAL;
    }
    if (!isOk(res)) {
        ALOGE("change uid(%u) rule of %d failed: %s, rule: %d, type: %d", uid, chain,
              res.msg().c_str(), rule, type);
        return -res.code();
    }
    return 0;
}

Status TrafficController::replaceUidsInMap(BpfMap<uint32_t, uint8_t>& map,
                                           const std::vector<int32_t>& uids, FirewallRule rule,
                                           FirewallType type) {
    std::set<int32_t> uidSet(uids.begin(), uids.end());
    std::vector<uint32_t> uidsToDelete;
    auto getUidsToDelete = [&uidsToDelete, &uidSet](const uint32_t& key,
                                                    const BpfMap<uint32_t, uint8_t>&) {
        if (key != UID_MAP_ENABLED && uidSet.find((int32_t)key) == uidSet.end()) {
            uidsToDelete.push_back(key);
        }
        return netdutils::status::ok;
    };
    RETURN_IF_NOT_OK(map.iterate(getUidsToDelete));

    for(auto uid : uidsToDelete) {
        RETURN_IF_NOT_OK(map.deleteValue(uid));
    }

    for (auto uid : uids) {
        RETURN_IF_NOT_OK(updateOwnerMapEntry(map, uid, rule, type));
    }
    return netdutils::status::ok;
}

int TrafficController::replaceUidOwnerMap(const std::string& name, bool isWhitelist,
                                          const std::vector<int32_t>& uids) {
    std::lock_guard<std::mutex> guard(mOwnerMatchMutex);
    FirewallRule rule;
    FirewallType type;
    if (isWhitelist) {
        type = WHITELIST;
        rule = ALLOW;
    } else {
        type = BLACKLIST;
        rule = DENY;
    }
    Status res;
    if (!name.compare(FirewallController::LOCAL_DOZABLE)) {
        res = replaceUidsInMap(mDozableUidMap, uids, rule, type);
    } else if (!name.compare(FirewallController::LOCAL_STANDBY)) {
        res = replaceUidsInMap(mStandbyUidMap, uids, rule, type);
    } else if (!name.compare(FirewallController::LOCAL_POWERSAVE)) {
        res = replaceUidsInMap(mPowerSaveUidMap, uids, rule, type);
    } else {
        ALOGE("unknown chain name: %s", name.c_str());
        return -EINVAL;
    }
    if (!isOk(res)) {
        ALOGE("Failed to clean up chain: %s: %s", name.c_str(), res.msg().c_str());
        return -res.code();
    }
    return 0;
}

int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
    std::lock_guard<std::mutex> guard(mOwnerMatchMutex);
    uint32_t keyUid = UID_MAP_ENABLED;
    uint8_t mapState = enable ? 1 : 0;
    Status res;
    switch (chain) {
        case DOZABLE:
            res = mDozableUidMap.writeValue(keyUid, mapState, BPF_EXIST);
            break;
        case STANDBY:
            res = mStandbyUidMap.writeValue(keyUid, mapState, BPF_EXIST);
            break;
        case POWERSAVE:
            res = mPowerSaveUidMap.writeValue(keyUid, mapState, BPF_EXIST);
            break;
        default:
            return -EINVAL;
    }
    if (!isOk(res)) {
        ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
    }
    return -res.code();
}

bool TrafficController::checkBpfStatsEnable() {
    return ebpfSupported;
}

std::string getProgramStatus(const char *path) {
    int ret = access(path, R_OK);
    if (ret == 0) {
        return StringPrintf("OK");
    }
    if (ret != 0 && errno == ENOENT) {
        return StringPrintf("program is missing at: %s", path);
    }
    return StringPrintf("check Program %s error: %s", path, strerror(errno));
}

std::string getMapStatus(const base::unique_fd& map_fd, const char* path) {
    if (map_fd.get() < 0) {
        return StringPrintf("map fd lost");
    }
    if (access(path, F_OK) != 0) {
        return StringPrintf("map not pinned to location: %s", path);
    }
    return StringPrintf("OK");
}

void dumpBpfMap(std::string mapName, DumpWriter& dw, const std::string& header) {
    dw.blankline();
    dw.println("%s:", mapName.c_str());
    if(!header.empty()) {
        dw.println(header.c_str());
    }
}

const String16 TrafficController::DUMP_KEYWORD = String16("trafficcontroller");

void TrafficController::dump(DumpWriter& dw, bool verbose) {
    std::lock_guard<std::mutex> ownerMapGuard(mOwnerMatchMutex);
    dw.incIndent();
    dw.println("TrafficController");

    dw.incIndent();
    dw.println("BPF module status: %s", ebpfSupported? "ON" : "OFF");

    if (!ebpfSupported)
        return;

    dw.blankline();
    dw.println("mCookieTagMap status: %s",
               getMapStatus(mCookieTagMap.getMap(), COOKIE_TAG_MAP_PATH).c_str());
    dw.println("mUidCounterSetMap status: %s",
               getMapStatus(mUidCounterSetMap.getMap(), UID_COUNTERSET_MAP_PATH).c_str());
    dw.println("mAppUidStatsMap status: %s",
               getMapStatus(mAppUidStatsMap.getMap(), APP_UID_STATS_MAP_PATH).c_str());
    dw.println("mUidStatsMap status: %s",
               getMapStatus(mUidStatsMap.getMap(), UID_STATS_MAP_PATH).c_str());
    dw.println("mTagStatsMap status: %s",
               getMapStatus(mTagStatsMap.getMap(), TAG_STATS_MAP_PATH).c_str());
    dw.println("mIfaceIndexNameMap status: %s",
               getMapStatus(mIfaceIndexNameMap.getMap(), IFACE_INDEX_NAME_MAP_PATH).c_str());
    dw.println("mIfaceStatsMap status: %s",
               getMapStatus(mIfaceStatsMap.getMap(), IFACE_STATS_MAP_PATH).c_str());
    dw.println("mDozableUidMap status: %s",
               getMapStatus(mDozableUidMap.getMap(), DOZABLE_UID_MAP_PATH).c_str());
    dw.println("mStandbyUidMap status: %s",
               getMapStatus(mStandbyUidMap.getMap(), STANDBY_UID_MAP_PATH).c_str());
    dw.println("mPowerSaveUidMap status: %s",
               getMapStatus(mPowerSaveUidMap.getMap(), POWERSAVE_UID_MAP_PATH).c_str());

    dw.blankline();
    dw.println("Cgroup ingress program status: %s",
               getProgramStatus(BPF_INGRESS_PROG_PATH).c_str());
    dw.println("Cgroup egress program status: %s", getProgramStatus(BPF_EGRESS_PROG_PATH).c_str());
    dw.println("xt_bpf ingress program status: %s",
               getProgramStatus(XT_BPF_INGRESS_PROG_PATH).c_str());
    dw.println("xt_bpf egress program status: %s",
               getProgramStatus(XT_BPF_EGRESS_PROG_PATH).c_str());

    if(!verbose) return;

    dw.blankline();
    dw.println("BPF map content:");

    dw.incIndent();

    // Print CookieTagMap content.
    dumpBpfMap("mCookieTagMap", dw, "");
    const auto printCookieTagInfo = [&dw](const uint64_t& key, const UidTag& value,
                                          const BpfMap<uint64_t, UidTag>&) {
        dw.println("cookie=%" PRIu64 " tag=0x%x uid=%u", key, value.tag, value.uid);
        return netdutils::status::ok;
    };
    Status res = mCookieTagMap.iterateWithValue(printCookieTagInfo);
    if (!isOk(res)) {
        dw.println("mCookieTagMap print end with error: %s", res.msg().c_str());
    }

    // Print UidCounterSetMap Content
    dumpBpfMap("mUidCounterSetMap", dw, "");
    const auto printUidInfo = [&dw](const uint32_t& key, const uint8_t& value,
                                    const BpfMap<uint32_t, uint8_t>&) {
        dw.println("%u %u", key, value);
        return netdutils::status::ok;
    };
    res = mUidCounterSetMap.iterateWithValue(printUidInfo);
    if (!isOk(res)) {
        dw.println("mUidCounterSetMap print end with error: %s", res.msg().c_str());
    }

    // Print AppUidStatsMap content
    std::string appUidStatsHeader = StringPrintf("uid rxBytes rxPackets txBytes txPackets");
    dumpBpfMap("mAppUidStatsMap:", dw, appUidStatsHeader);
    auto printAppUidStatsInfo = [&dw](const uint32_t& key, const StatsValue& value,
                                      const BpfMap<uint32_t, StatsValue>&) {
        dw.println("%u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, key, value.rxBytes,
                   value.rxPackets, value.txBytes, value.txPackets);
        return netdutils::status::ok;
    };
    res = mAppUidStatsMap.iterateWithValue(printAppUidStatsInfo);
    if (!res.ok()) {
        dw.println("mAppUidStatsMap print end with error: %s", res.msg().c_str());
    }

    // Print uidStatsMap content
    std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes"
                                           " rxPackets txBytes txPackets");
    dumpBpfMap("mUidStatsMap", dw, statsHeader);
    const auto printStatsInfo = [&dw, this](const StatsKey& key, const StatsValue& value,
                                            const BpfMap<StatsKey, StatsValue>&) {
        uint32_t ifIndex = key.ifaceIndex;
        auto ifname = mIfaceIndexNameMap.readValue(ifIndex);
        if (!isOk(ifname)) {
            strlcpy(ifname.value().name, "unknown", sizeof(IfaceValue));
        }
        dw.println("%u %s 0x%x %u %u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, ifIndex,
                   ifname.value().name, key.tag, key.uid, key.counterSet, value.rxBytes,
                   value.rxPackets, value.txBytes, value.txPackets);
        return netdutils::status::ok;
    };
    res = mUidStatsMap.iterateWithValue(printStatsInfo);
    if (!isOk(res)) {
        dw.println("mUidStatsMap print end with error: %s", res.msg().c_str());
    }

    // Print TagStatsMap content.
    dumpBpfMap("mTagStatsMap", dw, statsHeader);
    res = mTagStatsMap.iterateWithValue(printStatsInfo);
    if (!isOk(res)) {
        dw.println("mTagStatsMap print end with error: %s", res.msg().c_str());
    }

    // Print ifaceIndexToNameMap content.
    dumpBpfMap("mIfaceIndexNameMap", dw, "");
    const auto printIfaceNameInfo = [&dw](const uint32_t& key, const IfaceValue& value,
                                          const BpfMap<uint32_t, IfaceValue>&) {
        const char* ifname = value.name;
        dw.println("ifaceIndex=%u ifaceName=%s", key, ifname);
        return netdutils::status::ok;
    };
    res = mIfaceIndexNameMap.iterateWithValue(printIfaceNameInfo);
    if (!isOk(res)) {
        dw.println("mIfaceIndexNameMap print end with error: %s", res.msg().c_str());
    }

    // Print ifaceStatsMap content
    std::string ifaceStatsHeader = StringPrintf("ifaceIndex ifaceName rxBytes rxPackets txBytes"
                                                " txPackets");
    dumpBpfMap("mIfaceStatsMap:", dw, ifaceStatsHeader);
    const auto printIfaceStatsInfo = [&dw, this](const uint32_t& key, const StatsValue& value,
                                                 const BpfMap<uint32_t, StatsValue>&) {
        auto ifname = mIfaceIndexNameMap.readValue(key);
        if (!isOk(ifname)) {
            strlcpy(ifname.value().name, "unknown", sizeof(IfaceValue));
        }
        dw.println("%u %s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, key, ifname.value().name,
                   value.rxBytes, value.rxPackets, value.txBytes, value.txPackets);
        return netdutils::status::ok;
    };
    res = mIfaceStatsMap.iterateWithValue(printIfaceStatsInfo);
    if (!isOk(res)) {
        dw.println("mIfaceStatsMap print end with error: %s", res.msg().c_str());
    }

    // Print owner match uid maps
    dumpBpfMap("mDozableUidMap", dw, "");
    res = mDozableUidMap.iterateWithValue(printUidInfo);
    if (!isOk(res)) {
        dw.println("mDozableUidMap print end with error: %s", res.msg().c_str());
    }

    dumpBpfMap("mStandbyUidMap", dw, "");
    res = mStandbyUidMap.iterateWithValue(printUidInfo);
    if (!isOk(res)) {
        dw.println("mDozableUidMap print end with error: %s", res.msg().c_str());
    }

    dumpBpfMap("mPowerSaveUidMap", dw, "");
    res = mPowerSaveUidMap.iterateWithValue(printUidInfo);
    if (!isOk(res)) {
        dw.println("mDozableUidMap print end with error: %s", res.msg().c_str());
    }

    dw.decIndent();

    dw.decIndent();

    dw.decIndent();

}

}  // namespace net
}  // namespace android