| /* |
| * 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)); |
| |
| // TODO: if too many sockets are closed too quickly, we can overflow the socket buffer, and |
| // some entries in mCookieTagMap will not be freed. In order to fix this we would need to |
| // periodically dump all sockets and remove the tag entries for sockets that have been closed. |
| // For now, set a large-enough buffer that we can close hundreds of sockets without getting |
| // ENOBUFS and leaking mCookieTagMap entries. |
| int rcvbuf = 512 * 1024; |
| auto ret = sys.setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); |
| if (!ret.ok()) { |
| ALOGW("Failed to set SkDestroyListener buffer size to %d: %s", rcvbuf, ret.msg().c_str()); |
| } |
| |
| 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 |