/* * 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. */ #include #include #include #include #include #include #include "android-base/file.h" #include "android-base/strings.h" #include "android-base/unique_fd.h" #include "bpf/BpfMap.h" #include "netdbpf/BpfNetworkStats.h" #include "netdbpf/bpf_shared.h" #ifdef LOG_TAG #undef LOG_TAG #endif #define LOG_TAG "BpfNetworkStats" namespace android { namespace bpf { using base::Result; // The target map for stats reading should be the inactive map, which is oppsite // from the config value. static constexpr char const* STATS_MAP_PATH[] = {STATS_MAP_B_PATH, STATS_MAP_A_PATH}; int bpfGetUidStatsInternal(uid_t uid, Stats* stats, const BpfMap& appUidStatsMap) { auto statsEntry = appUidStatsMap.readValue(uid); if (statsEntry.ok()) { stats->rxPackets = statsEntry.value().rxPackets; stats->txPackets = statsEntry.value().txPackets; stats->rxBytes = statsEntry.value().rxBytes; stats->txBytes = statsEntry.value().txBytes; } return (statsEntry.ok() || statsEntry.error().code() == ENOENT) ? 0 : -statsEntry.error().code(); } int bpfGetUidStats(uid_t uid, Stats* stats) { BpfMapRO appUidStatsMap(APP_UID_STATS_MAP_PATH); if (!appUidStatsMap.isValid()) { int ret = -errno; ALOGE("Opening appUidStatsMap(%s) failed: %s", APP_UID_STATS_MAP_PATH, strerror(errno)); return ret; } return bpfGetUidStatsInternal(uid, stats, appUidStatsMap); } int bpfGetIfaceStatsInternal(const char* iface, Stats* stats, const BpfMap& ifaceStatsMap, const BpfMap& ifaceNameMap) { int64_t unknownIfaceBytesTotal = 0; stats->tcpRxPackets = -1; stats->tcpTxPackets = -1; const auto processIfaceStats = [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal]( const uint32_t& key, const BpfMap& ifaceStatsMap) -> Result { char ifname[IFNAMSIZ]; if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key, &unknownIfaceBytesTotal)) { return Result(); } if (!iface || !strcmp(iface, ifname)) { Result statsEntry = ifaceStatsMap.readValue(key); if (!statsEntry.ok()) { return statsEntry.error(); } stats->rxPackets += statsEntry.value().rxPackets; stats->txPackets += statsEntry.value().txPackets; stats->rxBytes += statsEntry.value().rxBytes; stats->txBytes += statsEntry.value().txBytes; } return Result(); }; auto res = ifaceStatsMap.iterate(processIfaceStats); return res.ok() ? 0 : -res.error().code(); } int bpfGetIfaceStats(const char* iface, Stats* stats) { BpfMapRO ifaceStatsMap(IFACE_STATS_MAP_PATH); int ret; if (!ifaceStatsMap.isValid()) { ret = -errno; ALOGE("get ifaceStats map fd failed: %s", strerror(errno)); return ret; } BpfMapRO ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH); if (!ifaceIndexNameMap.isValid()) { ret = -errno; ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno)); return ret; } return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap); } stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry, const char* ifname) { stats_line newLine; strlcpy(newLine.iface, ifname, sizeof(newLine.iface)); newLine.uid = (int32_t)statsKey.uid; newLine.set = (int32_t)statsKey.counterSet; newLine.tag = (int32_t)statsKey.tag; newLine.rxPackets = statsEntry.rxPackets; newLine.txPackets = statsEntry.txPackets; newLine.rxBytes = statsEntry.rxBytes; newLine.txBytes = statsEntry.txBytes; return newLine; } int parseBpfNetworkStatsDetailInternal(std::vector* lines, const std::vector& limitIfaces, int limitTag, int limitUid, const BpfMap& statsMap, const BpfMap& ifaceMap) { int64_t unknownIfaceBytesTotal = 0; const auto processDetailUidStats = [lines, &limitIfaces, &limitTag, &limitUid, &unknownIfaceBytesTotal, &ifaceMap]( const StatsKey& key, const BpfMap& statsMap) -> Result { char ifname[IFNAMSIZ]; if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key, &unknownIfaceBytesTotal)) { return Result(); } std::string ifnameStr(ifname); if (limitIfaces.size() > 0 && std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) { // Nothing matched; skip this line. return Result(); } if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) { return Result(); } if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) { return Result(); } Result statsEntry = statsMap.readValue(key); if (!statsEntry.ok()) { return base::ResultError(statsEntry.error().message(), statsEntry.error().code()); } lines->push_back(populateStatsEntry(key, statsEntry.value(), ifname)); return Result(); }; Result res = statsMap.iterate(processDetailUidStats); if (!res.ok()) { ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s", strerror(res.error().code())); return -res.error().code(); } // Since eBPF use hash map to record stats, network stats collected from // eBPF will be out of order. And the performance of findIndexHinted in // NetworkStats will also be impacted. // // Furthermore, since the StatsKey contains iface index, the network stats // reported to framework would create items with the same iface, uid, tag // and set, which causes NetworkStats maps wrong item to subtract. // // Thus, the stats needs to be properly sorted and grouped before reported. groupNetworkStats(lines); return 0; } int parseBpfNetworkStatsDetail(std::vector* lines, const std::vector& limitIfaces, int limitTag, int limitUid) { BpfMapRO ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH); if (!ifaceIndexNameMap.isValid()) { int ret = -errno; ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno)); return ret; } BpfMapRO configurationMap(CONFIGURATION_MAP_PATH); if (!configurationMap.isValid()) { int ret = -errno; ALOGE("get configuration map fd failed: %s", strerror(errno)); return ret; } auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY); if (!configuration.ok()) { ALOGE("Cannot read the old configuration from map: %s", configuration.error().message().c_str()); return -configuration.error().code(); } const char* statsMapPath = STATS_MAP_PATH[configuration.value()]; BpfMap statsMap(statsMapPath); if (!statsMap.isValid()) { int ret = -errno; ALOGE("get stats map fd failed: %s, path: %s", strerror(errno), statsMapPath); return ret; } // It is safe to read and clear the old map now since the // networkStatsFactory should call netd to swap the map in advance already. int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid, statsMap, ifaceIndexNameMap); if (ret) { ALOGE("parse detail network stats failed: %s", strerror(errno)); return ret; } Result res = statsMap.clear(); if (!res.ok()) { ALOGE("Clean up current stats map failed: %s", strerror(res.error().code())); return -res.error().code(); } return 0; } int parseBpfNetworkStatsDevInternal(std::vector* lines, const BpfMap& statsMap, const BpfMap& ifaceMap) { int64_t unknownIfaceBytesTotal = 0; const auto processDetailIfaceStats = [lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap]( const uint32_t& key, const StatsValue& value, const BpfMap&) { char ifname[IFNAMSIZ]; if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) { return Result(); } StatsKey fakeKey = { .uid = (uint32_t)UID_ALL, .tag = (uint32_t)TAG_NONE, .counterSet = (uint32_t)SET_ALL, }; lines->push_back(populateStatsEntry(fakeKey, value, ifname)); return Result(); }; Result res = statsMap.iterateWithValue(processDetailIfaceStats); if (!res.ok()) { ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s", strerror(res.error().code())); return -res.error().code(); } groupNetworkStats(lines); return 0; } int parseBpfNetworkStatsDev(std::vector* lines) { int ret = 0; BpfMapRO ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH); if (!ifaceIndexNameMap.isValid()) { ret = -errno; ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno)); return ret; } BpfMapRO ifaceStatsMap(IFACE_STATS_MAP_PATH); if (!ifaceStatsMap.isValid()) { ret = -errno; ALOGE("get ifaceStats map fd failed: %s", strerror(errno)); return ret; } return parseBpfNetworkStatsDevInternal(lines, ifaceStatsMap, ifaceIndexNameMap); } uint64_t combineUidTag(const uid_t uid, const uint32_t tag) { return (uint64_t)uid << 32 | tag; } void groupNetworkStats(std::vector* lines) { if (lines->size() <= 1) return; std::sort(lines->begin(), lines->end()); // Similar to std::unique(), but aggregates the duplicates rather than discarding them. size_t nextOutput = 0; for (size_t i = 1; i < lines->size(); i++) { if (lines->at(nextOutput) == lines->at(i)) { lines->at(nextOutput) += lines->at(i); } else { nextOutput++; if (nextOutput != i) { lines->at(nextOutput) = lines->at(i); } } } if (lines->size() != nextOutput + 1) { lines->resize(nextOutput + 1); } } // True if lhs equals to rhs, only compare iface, uid, tag and set. bool operator==(const stats_line& lhs, const stats_line& rhs) { return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) && !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface))); } // True if lhs is smaller then rhs, only compare iface, uid, tag and set. bool operator<(const stats_line& lhs, const stats_line& rhs) { int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)); if (ret != 0) return ret < 0; if (lhs.uid < rhs.uid) return true; if (lhs.uid > rhs.uid) return false; if (lhs.tag < rhs.tag) return true; if (lhs.tag > rhs.tag) return false; if (lhs.set < rhs.set) return true; if (lhs.set > rhs.set) return false; return false; } stats_line& stats_line::operator=(const stats_line& rhs) { if (this == &rhs) return *this; strlcpy(iface, rhs.iface, sizeof(iface)); uid = rhs.uid; set = rhs.set; tag = rhs.tag; rxPackets = rhs.rxPackets; txPackets = rhs.txPackets; rxBytes = rhs.rxBytes; txBytes = rhs.txBytes; return *this; } stats_line& stats_line::operator+=(const stats_line& rhs) { rxPackets += rhs.rxPackets; txPackets += rhs.txPackets; rxBytes += rhs.rxBytes; txBytes += rhs.txBytes; return *this; } } // namespace bpf } // namespace android