diff --git a/service-t/native/libs/libnetdbpf/Android.bp b/service-t/native/libs/libnetdbpf/Android.bp new file mode 100644 index 0000000000..d586d680c7 --- /dev/null +++ b/service-t/native/libs/libnetdbpf/Android.bp @@ -0,0 +1,63 @@ +// +// 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. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "system_netd_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_netd_license"], +} + +cc_library { + name: "libnetdbpf", + vendor_available: false, + host_supported: false, + header_libs: ["bpf_connectivity_headers"], + srcs: [ + "BpfNetworkStats.cpp" + ], + shared_libs: [ + "libbase", + "liblog", + "libnetdutils", + ], + export_include_dirs: ["include"], + defaults: ["netd_defaults"], + sanitize: { + cfi: true, + }, +} + +cc_test { + name: "libnetdbpf_test", + test_suites: ["device-tests"], + require_root: true, + header_libs: ["bpf_connectivity_headers"], + srcs: [ + "BpfNetworkStatsTest.cpp", + ], + defaults: ["netd_defaults"], + static_libs: ["libgmock"], + shared_libs: [ + "libbase", + "liblog", + "libnetdbpf", + "libnetdutils", + "libutils", + ], +} diff --git a/service-t/native/libs/libnetdbpf/BpfNetworkStats.cpp b/service-t/native/libs/libnetdbpf/BpfNetworkStats.cpp new file mode 100644 index 0000000000..2970503ff3 --- /dev/null +++ b/service-t/native/libs/libnetdbpf/BpfNetworkStats.cpp @@ -0,0 +1,350 @@ +/* + * 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 "bpf_shared.h" +#include "netdbpf/BpfNetworkStats.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 diff --git a/service-t/native/libs/libnetdbpf/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetdbpf/BpfNetworkStatsTest.cpp new file mode 100644 index 0000000000..fb8f0ec6ea --- /dev/null +++ b/service-t/native/libs/libnetdbpf/BpfNetworkStatsTest.cpp @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include "bpf/BpfMap.h" +#include "bpf/BpfUtils.h" +#include "netdbpf/BpfNetworkStats.h" + +using ::testing::Test; + +namespace android { +namespace bpf { + +using base::Result; +using base::unique_fd; + +constexpr int TEST_MAP_SIZE = 10; +constexpr uid_t TEST_UID1 = 10086; +constexpr uid_t TEST_UID2 = 12345; +constexpr uint32_t TEST_TAG = 42; +constexpr int TEST_COUNTERSET0 = 0; +constexpr int TEST_COUNTERSET1 = 1; +constexpr uint64_t TEST_BYTES0 = 1000; +constexpr uint64_t TEST_BYTES1 = 2000; +constexpr uint64_t TEST_PACKET0 = 100; +constexpr uint64_t TEST_PACKET1 = 200; +constexpr const char IFACE_NAME1[] = "lo"; +constexpr const char IFACE_NAME2[] = "wlan0"; +constexpr const char IFACE_NAME3[] = "rmnet_data0"; +// A iface name that the size is bigger then IFNAMSIZ +constexpr const char LONG_IFACE_NAME[] = "wlanWithALongName"; +constexpr const char TRUNCATED_IFACE_NAME[] = "wlanWithALongNa"; +constexpr uint32_t IFACE_INDEX1 = 1; +constexpr uint32_t IFACE_INDEX2 = 2; +constexpr uint32_t IFACE_INDEX3 = 3; +constexpr uint32_t IFACE_INDEX4 = 4; +constexpr uint32_t UNKNOWN_IFACE = 0; + +class BpfNetworkStatsHelperTest : public testing::Test { + protected: + BpfNetworkStatsHelperTest() {} + BpfMap mFakeCookieTagMap; + BpfMap mFakeAppUidStatsMap; + BpfMap mFakeStatsMap; + BpfMap mFakeIfaceIndexNameMap; + BpfMap mFakeIfaceStatsMap; + + void SetUp() { + ASSERT_EQ(0, setrlimitForTest()); + + mFakeCookieTagMap = BpfMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0); + ASSERT_LE(0, mFakeCookieTagMap.getMap()); + + mFakeAppUidStatsMap = BpfMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0); + ASSERT_LE(0, mFakeAppUidStatsMap.getMap()); + + mFakeStatsMap = BpfMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0); + ASSERT_LE(0, mFakeStatsMap.getMap()); + + mFakeIfaceIndexNameMap = BpfMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0); + ASSERT_LE(0, mFakeIfaceIndexNameMap.getMap()); + + mFakeIfaceStatsMap = BpfMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0); + ASSERT_LE(0, mFakeIfaceStatsMap.getMap()); + } + + void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) { + auto tagResult = mFakeCookieTagMap.readValue(cookie); + EXPECT_RESULT_OK(tagResult); + EXPECT_EQ(uid, tagResult.value().uid); + EXPECT_EQ(tag, tagResult.value().tag); + } + + void populateFakeStats(uid_t uid, uint32_t tag, uint32_t ifaceIndex, uint32_t counterSet, + StatsValue value, BpfMap& map) { + StatsKey key = { + .uid = (uint32_t)uid, .tag = tag, .counterSet = counterSet, .ifaceIndex = ifaceIndex}; + EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY)); + } + + void updateIfaceMap(const char* ifaceName, uint32_t ifaceIndex) { + IfaceValue iface; + strlcpy(iface.name, ifaceName, IFNAMSIZ); + EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY)); + } + + void expectStatsEqual(const StatsValue& target, const Stats& result) { + EXPECT_EQ(target.rxPackets, result.rxPackets); + EXPECT_EQ(target.rxBytes, result.rxBytes); + EXPECT_EQ(target.txPackets, result.txPackets); + EXPECT_EQ(target.txBytes, result.txBytes); + } + + void expectStatsLineEqual(const StatsValue target, const char* iface, uint32_t uid, + int counterSet, uint32_t tag, const stats_line& result) { + EXPECT_EQ(0, strcmp(iface, result.iface)); + EXPECT_EQ(uid, (uint32_t)result.uid); + EXPECT_EQ((uint32_t) counterSet, result.set); + EXPECT_EQ(tag, (uint32_t)result.tag); + EXPECT_EQ(target.rxPackets, (uint64_t)result.rxPackets); + EXPECT_EQ(target.rxBytes, (uint64_t)result.rxBytes); + EXPECT_EQ(target.txPackets, (uint64_t)result.txPackets); + EXPECT_EQ(target.txBytes, (uint64_t)result.txBytes); + } +}; + +// TEST to verify the behavior of bpf map when cocurrent deletion happens when +// iterating the same map. +TEST_F(BpfNetworkStatsHelperTest, TestIterateMapWithDeletion) { + for (int i = 0; i < 5; i++) { + uint64_t cookie = i + 1; + UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG}; + EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, tag, BPF_ANY)); + } + uint64_t curCookie = 0; + auto nextCookie = mFakeCookieTagMap.getNextKey(curCookie); + EXPECT_RESULT_OK(nextCookie); + uint64_t headOfMap = nextCookie.value(); + curCookie = nextCookie.value(); + // Find the second entry in the map, then immediately delete it. + nextCookie = mFakeCookieTagMap.getNextKey(curCookie); + EXPECT_RESULT_OK(nextCookie); + EXPECT_RESULT_OK(mFakeCookieTagMap.deleteValue((nextCookie.value()))); + // Find the entry that is now immediately after headOfMap, then delete that. + nextCookie = mFakeCookieTagMap.getNextKey(curCookie); + EXPECT_RESULT_OK(nextCookie); + EXPECT_RESULT_OK(mFakeCookieTagMap.deleteValue((nextCookie.value()))); + // Attempting to read an entry that has been deleted fails with ENOENT. + curCookie = nextCookie.value(); + auto tagResult = mFakeCookieTagMap.readValue(curCookie); + EXPECT_EQ(ENOENT, tagResult.error().code()); + // Finding the entry after our deleted entry restarts iteration from the beginning of the map. + nextCookie = mFakeCookieTagMap.getNextKey(curCookie); + EXPECT_RESULT_OK(nextCookie); + EXPECT_EQ(headOfMap, nextCookie.value()); +} + +TEST_F(BpfNetworkStatsHelperTest, TestBpfIterateMap) { + for (int i = 0; i < 5; i++) { + uint64_t cookie = i + 1; + UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG}; + EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, tag, BPF_ANY)); + } + int totalCount = 0; + int totalSum = 0; + const auto iterateWithoutDeletion = + [&totalCount, &totalSum](const uint64_t& key, const BpfMap&) { + EXPECT_GE((uint64_t)5, key); + totalCount++; + totalSum += key; + return Result(); + }; + EXPECT_RESULT_OK(mFakeCookieTagMap.iterate(iterateWithoutDeletion)); + EXPECT_EQ(5, totalCount); + EXPECT_EQ(1 + 2 + 3 + 4 + 5, totalSum); +} + +TEST_F(BpfNetworkStatsHelperTest, TestUidStatsNoTraffic) { + StatsValue value1 = { + .rxPackets = 0, + .rxBytes = 0, + .txPackets = 0, + .txBytes = 0, + }; + Stats result1 = {}; + ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap)); + expectStatsEqual(value1, result1); +} + +TEST_F(BpfNetworkStatsHelperTest, TestGetUidStatsTotal) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + updateIfaceMap(IFACE_NAME2, IFACE_INDEX2); + updateIfaceMap(IFACE_NAME3, IFACE_INDEX3); + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + StatsValue value2 = { + .rxPackets = TEST_PACKET0 * 2, + .rxBytes = TEST_BYTES0 * 2, + .txPackets = TEST_PACKET1 * 2, + .txBytes = TEST_BYTES1 * 2, + }; + ASSERT_RESULT_OK(mFakeAppUidStatsMap.writeValue(TEST_UID1, value1, BPF_ANY)); + ASSERT_RESULT_OK(mFakeAppUidStatsMap.writeValue(TEST_UID2, value2, BPF_ANY)); + Stats result1 = {}; + ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap)); + expectStatsEqual(value1, result1); + + Stats result2 = {}; + ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID2, &result2, mFakeAppUidStatsMap)); + expectStatsEqual(value2, result2); + std::vector lines; + std::vector ifaces; + populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap); + populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)2, lines.size()); + lines.clear(); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)1, lines.size()); + expectStatsLineEqual(value1, IFACE_NAME3, TEST_UID2, TEST_COUNTERSET1, 0, lines.front()); +} + +TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsInternal) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + updateIfaceMap(IFACE_NAME2, IFACE_INDEX2); + updateIfaceMap(IFACE_NAME3, IFACE_INDEX3); + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + StatsValue value2 = { + .rxPackets = TEST_PACKET1, + .rxBytes = TEST_BYTES1, + .txPackets = TEST_PACKET0, + .txBytes = TEST_BYTES0, + }; + uint32_t ifaceStatsKey = IFACE_INDEX1; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)); + ifaceStatsKey = IFACE_INDEX2; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY)); + ifaceStatsKey = IFACE_INDEX3; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)); + + Stats result1 = {}; + ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap, + mFakeIfaceIndexNameMap)); + expectStatsEqual(value1, result1); + Stats result2 = {}; + ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap, + mFakeIfaceIndexNameMap)); + expectStatsEqual(value2, result2); + Stats totalResult = {}; + ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap, + mFakeIfaceIndexNameMap)); + StatsValue totalValue = { + .rxPackets = TEST_PACKET0 * 2 + TEST_PACKET1, + .rxBytes = TEST_BYTES0 * 2 + TEST_BYTES1, + .txPackets = TEST_PACKET1 * 2 + TEST_PACKET0, + .txBytes = TEST_BYTES1 * 2 + TEST_BYTES0, + }; + expectStatsEqual(totalValue, totalResult); +} + +TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + updateIfaceMap(IFACE_NAME2, IFACE_INDEX2); + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value1, + mFakeStatsMap); + populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + std::vector lines; + std::vector ifaces; + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)4, lines.size()); + lines.clear(); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)3, lines.size()); + lines.clear(); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)2, lines.size()); + lines.clear(); + ifaces.push_back(std::string(IFACE_NAME1)); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)1, lines.size()); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines.front()); +} + +TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + updateIfaceMap(IFACE_NAME2, IFACE_INDEX2); + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + populateFakeStats(0, 0, 0, OVERFLOW_COUNTERSET, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap); + populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + std::vector lines; + std::vector ifaces; + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)4, lines.size()); + lines.clear(); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)3, lines.size()); + lines.clear(); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)1, lines.size()); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines.front()); + lines.clear(); + ifaces.push_back(std::string(IFACE_NAME1)); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1, + mFakeStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)2, lines.size()); +} + +TEST_F(BpfNetworkStatsHelperTest, TestUnkownIfaceError) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0 * 20, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1 * 20, + }; + uint32_t ifaceIndex = UNKNOWN_IFACE; + populateFakeStats(TEST_UID1, 0, ifaceIndex, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + StatsValue value2 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0 * 40, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1 * 40, + }; + populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap); + StatsKey curKey = { + .uid = TEST_UID1, + .tag = 0, + .counterSet = TEST_COUNTERSET0, + .ifaceIndex = ifaceIndex, + }; + char ifname[IFNAMSIZ]; + int64_t unknownIfaceBytesTotal = 0; + ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex, + ifname, curKey, &unknownIfaceBytesTotal)); + ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal); + curKey.ifaceIndex = IFACE_INDEX2; + ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex, + ifname, curKey, &unknownIfaceBytesTotal)); + ASSERT_EQ(-1, unknownIfaceBytesTotal); + std::vector lines; + std::vector ifaces; + // TODO: find a way to test the total of unknown Iface Bytes go above limit. + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)1, lines.size()); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front()); +} + +TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsDetail) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + updateIfaceMap(IFACE_NAME2, IFACE_INDEX2); + updateIfaceMap(IFACE_NAME3, IFACE_INDEX3); + updateIfaceMap(LONG_IFACE_NAME, IFACE_INDEX4); + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + StatsValue value2 = { + .rxPackets = TEST_PACKET1, + .rxBytes = TEST_BYTES1, + .txPackets = TEST_PACKET0, + .txBytes = TEST_BYTES0, + }; + uint32_t ifaceStatsKey = IFACE_INDEX1; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)); + ifaceStatsKey = IFACE_INDEX2; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY)); + ifaceStatsKey = IFACE_INDEX3; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)); + ifaceStatsKey = IFACE_INDEX4; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY)); + std::vector lines; + ASSERT_EQ(0, + parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((unsigned long)4, lines.size()); + + expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]); + expectStatsLineEqual(value1, IFACE_NAME3, UID_ALL, SET_ALL, TAG_NONE, lines[1]); + expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[2]); + ASSERT_EQ(0, strcmp(TRUNCATED_IFACE_NAME, lines[3].iface)); + expectStatsLineEqual(value2, TRUNCATED_IFACE_NAME, UID_ALL, SET_ALL, TAG_NONE, lines[3]); +} + +TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) { + // Create iface indexes with duplicate iface name. + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + updateIfaceMap(IFACE_NAME2, IFACE_INDEX2); + updateIfaceMap(IFACE_NAME1, IFACE_INDEX3); // Duplicate! + + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + StatsValue value2 = { + .rxPackets = TEST_PACKET1, + .rxBytes = TEST_BYTES1, + .txPackets = TEST_PACKET0, + .txBytes = TEST_BYTES0, + }; + StatsValue value3 = { + .rxPackets = TEST_PACKET0 * 2, + .rxBytes = TEST_BYTES0 * 2, + .txPackets = TEST_PACKET1 * 2, + .txBytes = TEST_BYTES1 * 2, + }; + + std::vector lines; + std::vector ifaces; + + // Test empty stats. + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((size_t) 0, lines.size()); + lines.clear(); + + // Test 1 line stats. + populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((size_t) 1, lines.size()); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]); + lines.clear(); + + // These items should not be grouped. + populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap); + populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET1, value2, mFakeStatsMap); + populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2, + mFakeStatsMap); + populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((size_t) 5, lines.size()); + lines.clear(); + + // These items should be grouped. + populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap); + + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((size_t) 5, lines.size()); + + // Verify Sorted & Grouped. + expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]); + expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]); + expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]); + expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]); + expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]); + lines.clear(); + + // Perform test on IfaceStats. + uint32_t ifaceStatsKey = IFACE_INDEX2; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY)); + ifaceStatsKey = IFACE_INDEX1; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)); + + // This should be grouped. + ifaceStatsKey = IFACE_INDEX3; + EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)); + + ASSERT_EQ(0, + parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap)); + ASSERT_EQ((size_t) 2, lines.size()); + + expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]); + expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[1]); + lines.clear(); +} + +// Test to verify that subtract overflow will not be triggered by the compare function invoked from +// sorting. See http:/b/119193941. +TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) { + updateIfaceMap(IFACE_NAME1, IFACE_INDEX1); + + StatsValue value1 = { + .rxPackets = TEST_PACKET0, + .rxBytes = TEST_BYTES0, + .txPackets = TEST_PACKET1, + .txBytes = TEST_BYTES1, + }; + + // Mutate uid, 0 < TEST_UID1 < INT_MAX < INT_MIN < UINT_MAX. + populateFakeStats(0, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(UINT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(INT_MIN, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(INT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + + // Mutate tag, 0 < TEST_TAG < INT_MAX < INT_MIN < UINT_MAX. + populateFakeStats(TEST_UID1, INT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, INT_MIN, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + populateFakeStats(TEST_UID1, UINT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap); + + // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible. + + std::vector lines; + std::vector ifaces; + ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap, + mFakeIfaceIndexNameMap)); + ASSERT_EQ((size_t) 8, lines.size()); + + // Uid 0 first + expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]); + + // Test uid, mutate tag. + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[1]); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[2]); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[3]); + expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[4]); + + // Mutate uid. + expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[5]); + expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[6]); + expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]); + lines.clear(); +} +} // namespace bpf +} // namespace android diff --git a/service-t/native/libs/libnetdbpf/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetdbpf/include/netdbpf/BpfNetworkStats.h new file mode 100644 index 0000000000..8ab7e2507c --- /dev/null +++ b/service-t/native/libs/libnetdbpf/include/netdbpf/BpfNetworkStats.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _BPF_NETWORKSTATS_H +#define _BPF_NETWORKSTATS_H + +#include +#include "bpf_shared.h" + +namespace android { +namespace bpf { + +// TODO: set this to a proper value based on the map size; +constexpr int TAG_STATS_MAP_SOFT_LIMIT = 3; +constexpr int UID_ALL = -1; +constexpr int TAG_ALL = -1; +constexpr int TAG_NONE = 0; +constexpr int SET_ALL = -1; +constexpr int SET_DEFAULT = 0; +constexpr int SET_FOREGROUND = 1; + +// The limit for stats received by a unknown interface; +constexpr const int64_t MAX_UNKNOWN_IFACE_BYTES = 100 * 1000; + +// This is used by +// frameworks/base/core/jni/com_android_internal_net_NetworkStatsFactory.cpp +// make sure it is consistent with the JNI code before changing this. +struct stats_line { + char iface[32]; + uint32_t uid; + uint32_t set; + uint32_t tag; + int64_t rxBytes; + int64_t rxPackets; + int64_t txBytes; + int64_t txPackets; + + stats_line& operator=(const stats_line& rhs); + stats_line& operator+=(const stats_line& rhs); +}; + +bool operator==(const stats_line& lhs, const stats_line& rhs); +bool operator<(const stats_line& lhs, const stats_line& rhs); + +// For test only +int bpfGetUidStatsInternal(uid_t uid, Stats* stats, + const BpfMap& appUidStatsMap); +// For test only +int bpfGetIfaceStatsInternal(const char* iface, Stats* stats, + const BpfMap& ifaceStatsMap, + const BpfMap& ifaceNameMap); +// For test only +int parseBpfNetworkStatsDetailInternal(std::vector* lines, + const std::vector& limitIfaces, int limitTag, + int limitUid, const BpfMap& statsMap, + const BpfMap& ifaceMap); +// For test only +int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap); +// For test only +template +int getIfaceNameFromMap(const BpfMap& ifaceMap, + const BpfMap& statsMap, uint32_t ifaceIndex, char* ifname, + const Key& curKey, int64_t* unknownIfaceBytesTotal) { + auto iface = ifaceMap.readValue(ifaceIndex); + if (!iface.ok()) { + maybeLogUnknownIface(ifaceIndex, statsMap, curKey, unknownIfaceBytesTotal); + return -ENODEV; + } + strlcpy(ifname, iface.value().name, sizeof(IfaceValue)); + return 0; +} + +template +void maybeLogUnknownIface(int ifaceIndex, const BpfMap& statsMap, + const Key& curKey, int64_t* unknownIfaceBytesTotal) { + // Have we already logged an error? + if (*unknownIfaceBytesTotal == -1) { + return; + } + + // Are we undercounting enough data to be worth logging? + auto statsEntry = statsMap.readValue(curKey); + if (!statsEntry.ok()) { + // No data is being undercounted. + return; + } + + *unknownIfaceBytesTotal += (statsEntry.value().rxBytes + statsEntry.value().txBytes); + if (*unknownIfaceBytesTotal >= MAX_UNKNOWN_IFACE_BYTES) { + ALOGE("Unknown name for ifindex %d with more than %" PRId64 " bytes of traffic", ifaceIndex, + *unknownIfaceBytesTotal); + *unknownIfaceBytesTotal = -1; + } +} + +// For test only +int parseBpfNetworkStatsDevInternal(std::vector* lines, + const BpfMap& statsMap, + const BpfMap& ifaceMap); + +int bpfGetUidStats(uid_t uid, Stats* stats); +int bpfGetIfaceStats(const char* iface, Stats* stats); +int parseBpfNetworkStatsDetail(std::vector* lines, + const std::vector& limitIfaces, int limitTag, + int limitUid); + +int parseBpfNetworkStatsDev(std::vector* lines); +void groupNetworkStats(std::vector* lines); +int cleanStatsMap(); +} // namespace bpf +} // namespace android + +#endif // _BPF_NETWORKSTATS_H