|  | /* | 
|  | * 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 "TrafficController" | 
|  | #include <inttypes.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 <map> | 
|  | #include <mutex> | 
|  | #include <unordered_set> | 
|  | #include <vector> | 
|  |  | 
|  | #include <android-base/stringprintf.h> | 
|  | #include <android-base/strings.h> | 
|  | #include <android-base/unique_fd.h> | 
|  | #include <netdutils/StatusOr.h> | 
|  | #include <netdutils/Syscalls.h> | 
|  | #include <netdutils/UidConstants.h> | 
|  | #include <netdutils/Utils.h> | 
|  | #include <private/android_filesystem_config.h> | 
|  |  | 
|  | #include "TrafficController.h" | 
|  | #include "bpf/BpfMap.h" | 
|  | #include "netdutils/DumpWriter.h" | 
|  |  | 
|  | namespace android { | 
|  | namespace net { | 
|  |  | 
|  | using base::StringPrintf; | 
|  | using base::unique_fd; | 
|  | using bpf::BpfMap; | 
|  | using bpf::synchronizeKernelRCU; | 
|  | using netdutils::DumpWriter; | 
|  | using netdutils::getIfaceList; | 
|  | using netdutils::NetlinkListener; | 
|  | using netdutils::NetlinkListenerInterface; | 
|  | using netdutils::ScopedIndent; | 
|  | using netdutils::Slice; | 
|  | using netdutils::sSyscalls; | 
|  | using netdutils::Status; | 
|  | using netdutils::statusFromErrno; | 
|  | using netdutils::StatusOr; | 
|  |  | 
|  | constexpr int kSockDiagMsgType = SOCK_DIAG_BY_FAMILY; | 
|  | constexpr int kSockDiagDoneMsgType = NLMSG_DONE; | 
|  |  | 
|  | const char* TrafficController::LOCAL_DOZABLE = "fw_dozable"; | 
|  | const char* TrafficController::LOCAL_STANDBY = "fw_standby"; | 
|  | const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave"; | 
|  | const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted"; | 
|  | const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby"; | 
|  |  | 
|  | static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET, | 
|  | "Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET"); | 
|  | static_assert(BPF_PERMISSION_UPDATE_DEVICE_STATS == INetd::PERMISSION_UPDATE_DEVICE_STATS, | 
|  | "Mismatch between BPF and AIDL permissions: PERMISSION_UPDATE_DEVICE_STATS"); | 
|  |  | 
|  | #define FLAG_MSG_TRANS(result, flag, value) \ | 
|  | do {                                    \ | 
|  | if ((value) & (flag)) {             \ | 
|  | (result).append(" " #flag);     \ | 
|  | (value) &= ~(flag);             \ | 
|  | }                                   \ | 
|  | } while (0) | 
|  |  | 
|  | const std::string uidMatchTypeToString(uint8_t match) { | 
|  | std::string matchType; | 
|  | FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, DOZABLE_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match); | 
|  | FLAG_MSG_TRANS(matchType, IIF_MATCH, match); | 
|  | if (match) { | 
|  | return StringPrintf("Unknown match: %u", match); | 
|  | } | 
|  | return matchType; | 
|  | } | 
|  |  | 
|  | bool TrafficController::hasUpdateDeviceStatsPermission(uid_t uid) { | 
|  | // This implementation is the same logic as method ActivityManager#checkComponentPermission. | 
|  | // It implies that the calling uid can never be the same as PER_USER_RANGE. | 
|  | uint32_t appId = uid % PER_USER_RANGE; | 
|  | return ((appId == AID_ROOT) || (appId == AID_SYSTEM) || | 
|  | mPrivilegedUser.find(appId) != mPrivilegedUser.end()); | 
|  | } | 
|  |  | 
|  | const std::string UidPermissionTypeToString(int permission) { | 
|  | if (permission == INetd::PERMISSION_NONE) { | 
|  | return "PERMISSION_NONE"; | 
|  | } | 
|  | if (permission == INetd::PERMISSION_UNINSTALLED) { | 
|  | // This should never appear in the map, complain loudly if it does. | 
|  | return "PERMISSION_UNINSTALLED error!"; | 
|  | } | 
|  | std::string permissionType; | 
|  | FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_INTERNET, permission); | 
|  | FLAG_MSG_TRANS(permissionType, BPF_PERMISSION_UPDATE_DEVICE_STATS, permission); | 
|  | if (permission) { | 
|  | return StringPrintf("Unknown permission: %u", permission); | 
|  | } | 
|  | return permissionType; | 
|  | } | 
|  |  | 
|  | StatusOr<std::unique_ptr<NetlinkListenerInterface>> TrafficController::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), "SkDestroyListen"); | 
|  |  | 
|  | return listener; | 
|  | } | 
|  |  | 
|  | Status TrafficController::initMaps() { | 
|  | std::lock_guard guard(mMutex); | 
|  |  | 
|  | RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH)); | 
|  | RETURN_IF_NOT_OK(mUidCounterSetMap.init(UID_COUNTERSET_MAP_PATH)); | 
|  | RETURN_IF_NOT_OK(mAppUidStatsMap.init(APP_UID_STATS_MAP_PATH)); | 
|  | RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH)); | 
|  | RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH)); | 
|  | RETURN_IF_NOT_OK(mIfaceIndexNameMap.init(IFACE_INDEX_NAME_MAP_PATH)); | 
|  | RETURN_IF_NOT_OK(mIfaceStatsMap.init(IFACE_STATS_MAP_PATH)); | 
|  |  | 
|  | RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH)); | 
|  | RETURN_IF_NOT_OK( | 
|  | mConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY, DEFAULT_CONFIG, BPF_ANY)); | 
|  | RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A, | 
|  | BPF_ANY)); | 
|  |  | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH)); | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.clear()); | 
|  | RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH)); | 
|  |  | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | Status TrafficController::start() { | 
|  | 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. | 
|  | // TODO: Clean-up addInterface() after interface monitoring is in | 
|  | // NetworkStatsService. | 
|  | std::map<std::string, uint32_t> ifacePairs; | 
|  | ASSIGN_OR_RETURN(ifacePairs, 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) { | 
|  | std::lock_guard guard(mMutex); | 
|  | 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); | 
|  |  | 
|  | Status s = mCookieTagMap.deleteValue(sock_cookie); | 
|  | if (!isOk(s) && s.code() != ENOENT) { | 
|  | ALOGE("Failed to delete cookie %" PRIx64 ": %s", sock_cookie, toString(s).c_str()); | 
|  | return; | 
|  | } | 
|  | }; | 
|  | 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)); | 
|  |  | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) { | 
|  | 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(UidOwnerMatchType match, uid_t uid, FirewallRule rule, | 
|  | FirewallType type) { | 
|  | std::lock_guard guard(mMutex); | 
|  | if ((rule == ALLOW && type == ALLOWLIST) || (rule == DENY && type == DENYLIST)) { | 
|  | RETURN_IF_NOT_OK(addRule(uid, match)); | 
|  | } else if ((rule == ALLOW && type == DENYLIST) || (rule == DENY && type == ALLOWLIST)) { | 
|  | RETURN_IF_NOT_OK(removeRule(uid, match)); | 
|  | } else { | 
|  | //Cannot happen. | 
|  | return statusFromErrno(EINVAL, ""); | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | Status TrafficController::removeRule(uint32_t uid, UidOwnerMatchType match) { | 
|  | auto oldMatch = mUidOwnerMap.readValue(uid); | 
|  | if (oldMatch.ok()) { | 
|  | UidOwnerValue newMatch = { | 
|  | .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif, | 
|  | .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match), | 
|  | }; | 
|  | if (newMatch.rule == 0) { | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid)); | 
|  | } else { | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY)); | 
|  | } | 
|  | } else { | 
|  | return statusFromErrno(ENOENT, StringPrintf("uid: %u does not exist in map", uid)); | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | Status TrafficController::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) { | 
|  | // iif should be non-zero if and only if match == MATCH_IIF | 
|  | if (match == IIF_MATCH && iif == 0) { | 
|  | return statusFromErrno(EINVAL, "Interface match must have nonzero interface index"); | 
|  | } else if (match != IIF_MATCH && iif != 0) { | 
|  | return statusFromErrno(EINVAL, "Non-interface match must have zero interface index"); | 
|  | } | 
|  | auto oldMatch = mUidOwnerMap.readValue(uid); | 
|  | if (oldMatch.ok()) { | 
|  | UidOwnerValue newMatch = { | 
|  | .iif = iif ? iif : oldMatch.value().iif, | 
|  | .rule = static_cast<uint8_t>(oldMatch.value().rule | match), | 
|  | }; | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY)); | 
|  | } else { | 
|  | UidOwnerValue newMatch = { | 
|  | .iif = iif, | 
|  | .rule = static_cast<uint8_t>(match), | 
|  | }; | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY)); | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | Status TrafficController::updateUidOwnerMap(const uint32_t uid, | 
|  | UidOwnerMatchType matchType, IptOp op) { | 
|  | std::lock_guard guard(mMutex); | 
|  | if (op == IptOpDelete) { | 
|  | RETURN_IF_NOT_OK(removeRule(uid, matchType)); | 
|  | } else if (op == IptOpInsert) { | 
|  | RETURN_IF_NOT_OK(addRule(uid, matchType)); | 
|  | } else { | 
|  | // Cannot happen. | 
|  | return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, matchType)); | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | FirewallType TrafficController::getFirewallType(ChildChain chain) { | 
|  | switch (chain) { | 
|  | case DOZABLE: | 
|  | return ALLOWLIST; | 
|  | case STANDBY: | 
|  | return DENYLIST; | 
|  | case POWERSAVE: | 
|  | return ALLOWLIST; | 
|  | case RESTRICTED: | 
|  | return ALLOWLIST; | 
|  | case LOW_POWER_STANDBY: | 
|  | return ALLOWLIST; | 
|  | case NONE: | 
|  | default: | 
|  | return DENYLIST; | 
|  | } | 
|  | } | 
|  |  | 
|  | int TrafficController::changeUidOwnerRule(ChildChain chain, uid_t uid, FirewallRule rule, | 
|  | FirewallType type) { | 
|  | Status res; | 
|  | switch (chain) { | 
|  | case DOZABLE: | 
|  | res = updateOwnerMapEntry(DOZABLE_MATCH, uid, rule, type); | 
|  | break; | 
|  | case STANDBY: | 
|  | res = updateOwnerMapEntry(STANDBY_MATCH, uid, rule, type); | 
|  | break; | 
|  | case POWERSAVE: | 
|  | res = updateOwnerMapEntry(POWERSAVE_MATCH, uid, rule, type); | 
|  | break; | 
|  | case RESTRICTED: | 
|  | res = updateOwnerMapEntry(RESTRICTED_MATCH, uid, rule, type); | 
|  | break; | 
|  | case LOW_POWER_STANDBY: | 
|  | res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type); | 
|  | break; | 
|  | case NONE: | 
|  | default: | 
|  | ALOGW("Unknown child chain: %d", chain); | 
|  | 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::replaceRulesInMap(const UidOwnerMatchType match, | 
|  | const std::vector<int32_t>& uids) { | 
|  | std::lock_guard guard(mMutex); | 
|  | 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, UidOwnerValue>&) { | 
|  | if (uidSet.find((int32_t) key) == uidSet.end()) { | 
|  | uidsToDelete.push_back(key); | 
|  | } | 
|  | return base::Result<void>(); | 
|  | }; | 
|  | RETURN_IF_NOT_OK(mUidOwnerMap.iterate(getUidsToDelete)); | 
|  |  | 
|  | for(auto uid : uidsToDelete) { | 
|  | RETURN_IF_NOT_OK(removeRule(uid, match)); | 
|  | } | 
|  |  | 
|  | for (auto uid : uids) { | 
|  | RETURN_IF_NOT_OK(addRule(uid, match)); | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | Status TrafficController::addUidInterfaceRules(const int iif, | 
|  | const std::vector<int32_t>& uidsToAdd) { | 
|  | if (!iif) { | 
|  | return statusFromErrno(EINVAL, "Interface rule must specify interface"); | 
|  | } | 
|  | std::lock_guard guard(mMutex); | 
|  |  | 
|  | for (auto uid : uidsToAdd) { | 
|  | netdutils::Status result = addRule(uid, IIF_MATCH, iif); | 
|  | if (!isOk(result)) { | 
|  | ALOGW("addRule failed(%d): uid=%d iif=%d", result.code(), uid, iif); | 
|  | } | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | Status TrafficController::removeUidInterfaceRules(const std::vector<int32_t>& uidsToDelete) { | 
|  | std::lock_guard guard(mMutex); | 
|  |  | 
|  | for (auto uid : uidsToDelete) { | 
|  | netdutils::Status result = removeRule(uid, IIF_MATCH); | 
|  | if (!isOk(result)) { | 
|  | ALOGW("removeRule failed(%d): uid=%d", result.code(), uid); | 
|  | } | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | int TrafficController::replaceUidOwnerMap(const std::string& name, bool isAllowlist __unused, | 
|  | const std::vector<int32_t>& uids) { | 
|  | // FirewallRule rule = isAllowlist ? ALLOW : DENY; | 
|  | // FirewallType type = isAllowlist ? ALLOWLIST : DENYLIST; | 
|  | Status res; | 
|  | if (!name.compare(LOCAL_DOZABLE)) { | 
|  | res = replaceRulesInMap(DOZABLE_MATCH, uids); | 
|  | } else if (!name.compare(LOCAL_STANDBY)) { | 
|  | res = replaceRulesInMap(STANDBY_MATCH, uids); | 
|  | } else if (!name.compare(LOCAL_POWERSAVE)) { | 
|  | res = replaceRulesInMap(POWERSAVE_MATCH, uids); | 
|  | } else if (!name.compare(LOCAL_RESTRICTED)) { | 
|  | res = replaceRulesInMap(RESTRICTED_MATCH, uids); | 
|  | } else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) { | 
|  | res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids); | 
|  | } 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 guard(mMutex); | 
|  | uint32_t key = UID_RULES_CONFIGURATION_KEY; | 
|  | auto oldConfiguration = mConfigurationMap.readValue(key); | 
|  | if (!oldConfiguration.ok()) { | 
|  | ALOGE("Cannot read the old configuration from map: %s", | 
|  | oldConfiguration.error().message().c_str()); | 
|  | return -oldConfiguration.error().code(); | 
|  | } | 
|  | Status res; | 
|  | BpfConfig newConfiguration; | 
|  | uint8_t match; | 
|  | switch (chain) { | 
|  | case DOZABLE: | 
|  | match = DOZABLE_MATCH; | 
|  | break; | 
|  | case STANDBY: | 
|  | match = STANDBY_MATCH; | 
|  | break; | 
|  | case POWERSAVE: | 
|  | match = POWERSAVE_MATCH; | 
|  | break; | 
|  | case RESTRICTED: | 
|  | match = RESTRICTED_MATCH; | 
|  | break; | 
|  | case LOW_POWER_STANDBY: | 
|  | match = LOW_POWER_STANDBY_MATCH; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | newConfiguration = | 
|  | enable ? (oldConfiguration.value() | match) : (oldConfiguration.value() & (~match)); | 
|  | res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST); | 
|  | if (!isOk(res)) { | 
|  | ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str()); | 
|  | } | 
|  | return -res.code(); | 
|  | } | 
|  |  | 
|  | Status TrafficController::swapActiveStatsMap() { | 
|  | std::lock_guard guard(mMutex); | 
|  |  | 
|  | uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY; | 
|  | auto oldConfiguration = mConfigurationMap.readValue(key); | 
|  | if (!oldConfiguration.ok()) { | 
|  | ALOGE("Cannot read the old configuration from map: %s", | 
|  | oldConfiguration.error().message().c_str()); | 
|  | return Status(oldConfiguration.error().code(), oldConfiguration.error().message()); | 
|  | } | 
|  |  | 
|  | // Write to the configuration map to inform the kernel eBPF program to switch | 
|  | // from using one map to the other. Use flag BPF_EXIST here since the map should | 
|  | // be already populated in initMaps. | 
|  | uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A; | 
|  | auto res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure, | 
|  | BPF_EXIST); | 
|  | if (!res.ok()) { | 
|  | ALOGE("Failed to toggle the stats map: %s", strerror(res.error().code())); | 
|  | return res; | 
|  | } | 
|  | // After changing the config, we need to make sure all the current running | 
|  | // eBPF programs are finished and all the CPUs are aware of this config change | 
|  | // before we modify the old map. So we do a special hack here to wait for | 
|  | // the kernel to do a synchronize_rcu(). Once the kernel called | 
|  | // synchronize_rcu(), the config we just updated will be available to all cores | 
|  | // and the next eBPF programs triggered inside the kernel will use the new | 
|  | // map configuration. So once this function returns we can safely modify the | 
|  | // old stats map without concerning about race between the kernel and | 
|  | // userspace. | 
|  | int ret = synchronizeKernelRCU(); | 
|  | if (ret) { | 
|  | ALOGE("map swap synchronize_rcu() ended with failure: %s", strerror(-ret)); | 
|  | return statusFromErrno(-ret, "map swap synchronize_rcu() failed"); | 
|  | } | 
|  | return netdutils::status::ok; | 
|  | } | 
|  |  | 
|  | void TrafficController::setPermissionForUids(int permission, const std::vector<uid_t>& uids) { | 
|  | std::lock_guard guard(mMutex); | 
|  | if (permission == INetd::PERMISSION_UNINSTALLED) { | 
|  | for (uid_t uid : uids) { | 
|  | // Clean up all permission information for the related uid if all the | 
|  | // packages related to it are uninstalled. | 
|  | mPrivilegedUser.erase(uid); | 
|  | Status ret = mUidPermissionMap.deleteValue(uid); | 
|  | if (!isOk(ret) && ret.code() != ENOENT) { | 
|  | ALOGE("Failed to clean up the permission for %u: %s", uid, strerror(ret.code())); | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | bool privileged = (permission & INetd::PERMISSION_UPDATE_DEVICE_STATS); | 
|  |  | 
|  | for (uid_t uid : uids) { | 
|  | if (privileged) { | 
|  | mPrivilegedUser.insert(uid); | 
|  | } else { | 
|  | mPrivilegedUser.erase(uid); | 
|  | } | 
|  |  | 
|  | // The map stores all the permissions that the UID has, except if the only permission | 
|  | // the UID has is the INTERNET permission, then the UID should not appear in the map. | 
|  | if (permission != INetd::PERMISSION_INTERNET) { | 
|  | Status ret = mUidPermissionMap.writeValue(uid, permission, BPF_ANY); | 
|  | if (!isOk(ret)) { | 
|  | ALOGE("Failed to set permission: %s of uid(%u) to permission map: %s", | 
|  | UidPermissionTypeToString(permission).c_str(), uid, strerror(ret.code())); | 
|  | } | 
|  | } else { | 
|  | Status ret = mUidPermissionMap.deleteValue(uid); | 
|  | if (!isOk(ret) && ret.code() != ENOENT) { | 
|  | ALOGE("Failed to remove uid %u from permission map: %s", uid, strerror(ret.code())); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | 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"); | 
|  | } | 
|  |  | 
|  | // NOLINTNEXTLINE(google-runtime-references): grandfathered pass by non-const reference | 
|  | void dumpBpfMap(const std::string& mapName, DumpWriter& dw, const std::string& header) { | 
|  | dw.blankline(); | 
|  | dw.println("%s:", mapName.c_str()); | 
|  | if (!header.empty()) { | 
|  | dw.println(header); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TrafficController::dump(int fd, bool verbose) { | 
|  | std::lock_guard guard(mMutex); | 
|  | DumpWriter dw(fd); | 
|  |  | 
|  | ScopedIndent indentTop(dw); | 
|  | dw.println("TrafficController"); | 
|  |  | 
|  | ScopedIndent indentPreBpfModule(dw); | 
|  |  | 
|  | 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("mStatsMapA status: %s", | 
|  | getMapStatus(mStatsMapA.getMap(), STATS_MAP_A_PATH).c_str()); | 
|  | dw.println("mStatsMapB status: %s", | 
|  | getMapStatus(mStatsMapB.getMap(), STATS_MAP_B_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("mConfigurationMap status: %s", | 
|  | getMapStatus(mConfigurationMap.getMap(), CONFIGURATION_MAP_PATH).c_str()); | 
|  | dw.println("mUidOwnerMap status: %s", | 
|  | getMapStatus(mUidOwnerMap.getMap(), UID_OWNER_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()); | 
|  | dw.println("xt_bpf bandwidth allowlist program status: %s", | 
|  | getProgramStatus(XT_BPF_ALLOWLIST_PROG_PATH).c_str()); | 
|  | dw.println("xt_bpf bandwidth denylist program status: %s", | 
|  | getProgramStatus(XT_BPF_DENYLIST_PROG_PATH).c_str()); | 
|  |  | 
|  | if (!verbose) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | dw.blankline(); | 
|  | dw.println("BPF map content:"); | 
|  |  | 
|  | ScopedIndent indentForMapContent(dw); | 
|  |  | 
|  | // Print CookieTagMap content. | 
|  | dumpBpfMap("mCookieTagMap", dw, ""); | 
|  | const auto printCookieTagInfo = [&dw](const uint64_t& key, const UidTagValue& value, | 
|  | const BpfMap<uint64_t, UidTagValue>&) { | 
|  | dw.println("cookie=%" PRIu64 " tag=0x%x uid=%u", key, value.tag, value.uid); | 
|  | return base::Result<void>(); | 
|  | }; | 
|  | base::Result<void> res = mCookieTagMap.iterateWithValue(printCookieTagInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mCookieTagMap print end with error: %s", res.error().message().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 base::Result<void>(); | 
|  | }; | 
|  | res = mUidCounterSetMap.iterateWithValue(printUidInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mUidCounterSetMap print end with error: %s", res.error().message().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 base::Result<void>(); | 
|  | }; | 
|  | res = mAppUidStatsMap.iterateWithValue(printAppUidStatsInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mAppUidStatsMap print end with error: %s", res.error().message().c_str()); | 
|  | } | 
|  |  | 
|  | // Print uidStatsMap content. | 
|  | std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes" | 
|  | " rxPackets txBytes txPackets"); | 
|  | dumpBpfMap("mStatsMapA", 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 (!ifname.ok()) { | 
|  | ifname = IfaceValue{"unknown"}; | 
|  | } | 
|  | 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 base::Result<void>(); | 
|  | }; | 
|  | res = mStatsMapA.iterateWithValue(printStatsInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mStatsMapA print end with error: %s", res.error().message().c_str()); | 
|  | } | 
|  |  | 
|  | // Print TagStatsMap content. | 
|  | dumpBpfMap("mStatsMapB", dw, statsHeader); | 
|  | res = mStatsMapB.iterateWithValue(printStatsInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mStatsMapB print end with error: %s", res.error().message().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 base::Result<void>(); | 
|  | }; | 
|  | res = mIfaceIndexNameMap.iterateWithValue(printIfaceNameInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mIfaceIndexNameMap print end with error: %s", res.error().message().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 (!ifname.ok()) { | 
|  | ifname = IfaceValue{"unknown"}; | 
|  | } | 
|  | dw.println("%u %s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, key, ifname.value().name, | 
|  | value.rxBytes, value.rxPackets, value.txBytes, value.txPackets); | 
|  | return base::Result<void>(); | 
|  | }; | 
|  | res = mIfaceStatsMap.iterateWithValue(printIfaceStatsInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mIfaceStatsMap print end with error: %s", res.error().message().c_str()); | 
|  | } | 
|  |  | 
|  | dw.blankline(); | 
|  |  | 
|  | uint32_t key = UID_RULES_CONFIGURATION_KEY; | 
|  | auto configuration = mConfigurationMap.readValue(key); | 
|  | if (configuration.ok()) { | 
|  | dw.println("current ownerMatch configuration: %d%s", configuration.value(), | 
|  | uidMatchTypeToString(configuration.value()).c_str()); | 
|  | } else { | 
|  | dw.println("mConfigurationMap read ownerMatch configure failed with error: %s", | 
|  | configuration.error().message().c_str()); | 
|  | } | 
|  |  | 
|  | key = CURRENT_STATS_MAP_CONFIGURATION_KEY; | 
|  | configuration = mConfigurationMap.readValue(key); | 
|  | if (configuration.ok()) { | 
|  | const char* statsMapDescription = "???"; | 
|  | switch (configuration.value()) { | 
|  | case SELECT_MAP_A: | 
|  | statsMapDescription = "SELECT_MAP_A"; | 
|  | break; | 
|  | case SELECT_MAP_B: | 
|  | statsMapDescription = "SELECT_MAP_B"; | 
|  | break; | 
|  | // No default clause, so if we ever add a third map, this code will fail to build. | 
|  | } | 
|  | dw.println("current statsMap configuration: %d %s", configuration.value(), | 
|  | statsMapDescription); | 
|  | } else { | 
|  | dw.println("mConfigurationMap read stats map configure failed with error: %s", | 
|  | configuration.error().message().c_str()); | 
|  | } | 
|  | dumpBpfMap("mUidOwnerMap", dw, ""); | 
|  | const auto printUidMatchInfo = [&dw, this](const uint32_t& key, const UidOwnerValue& value, | 
|  | const BpfMap<uint32_t, UidOwnerValue>&) { | 
|  | if (value.rule & IIF_MATCH) { | 
|  | auto ifname = mIfaceIndexNameMap.readValue(value.iif); | 
|  | if (ifname.ok()) { | 
|  | dw.println("%u %s %s", key, uidMatchTypeToString(value.rule).c_str(), | 
|  | ifname.value().name); | 
|  | } else { | 
|  | dw.println("%u %s %u", key, uidMatchTypeToString(value.rule).c_str(), value.iif); | 
|  | } | 
|  | } else { | 
|  | dw.println("%u %s", key, uidMatchTypeToString(value.rule).c_str()); | 
|  | } | 
|  | return base::Result<void>(); | 
|  | }; | 
|  | res = mUidOwnerMap.iterateWithValue(printUidMatchInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mUidOwnerMap print end with error: %s", res.error().message().c_str()); | 
|  | } | 
|  | dumpBpfMap("mUidPermissionMap", dw, ""); | 
|  | const auto printUidPermissionInfo = [&dw](const uint32_t& key, const int& value, | 
|  | const BpfMap<uint32_t, uint8_t>&) { | 
|  | dw.println("%u %s", key, UidPermissionTypeToString(value).c_str()); | 
|  | return base::Result<void>(); | 
|  | }; | 
|  | res = mUidPermissionMap.iterateWithValue(printUidPermissionInfo); | 
|  | if (!res.ok()) { | 
|  | dw.println("mUidPermissionMap print end with error: %s", res.error().message().c_str()); | 
|  | } | 
|  |  | 
|  | dumpBpfMap("mPrivilegedUser", dw, ""); | 
|  | for (uid_t uid : mPrivilegedUser) { | 
|  | dw.println("%u ALLOW_UPDATE_DEVICE_STATS", (uint32_t)uid); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace net | 
|  | }  // namespace android |