Copy libnetdbpf from system/net to tethering module
This is a clean copy (that can't build). Modifications will be in
followed commits.
Bug: 202086915
Test: $ diff -r
packages/modules/Connectivity/service-t/native/libs/libnetdbpf/
system/netd/libnetdbpf/
(no different)
No-Typo-Check: Clean move
Change-Id: I23aad26d487b4d99e24ffecf79eeef3f8eea664b
diff --git a/service-t/native/libs/libnetdbpf/Android.bp b/service-t/native/libs/libnetdbpf/Android.bp
new file mode 100644
index 0000000..d586d68
--- /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 0000000..2970503
--- /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 <inttypes.h>
+#include <net/if.h>
+#include <string.h>
+#include <unordered_set>
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#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<uint32_t, StatsValue>& 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<uint32_t, StatsValue> 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<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ stats->tcpRxPackets = -1;
+ stats->tcpTxPackets = -1;
+ const auto processIfaceStats =
+ [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
+ const uint32_t& key,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap) -> Result<void> {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key,
+ &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ if (!iface || !strcmp(iface, ifname)) {
+ Result<StatsValue> 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<void>();
+ };
+ auto res = ifaceStatsMap.iterate(processIfaceStats);
+ return res.ok() ? 0 : -res.error().code();
+}
+
+int bpfGetIfaceStats(const char* iface, Stats* stats) {
+ BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ int ret;
+ if (!ifaceStatsMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ BpfMapRO<uint32_t, IfaceValue> 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<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ const auto processDetailUidStats =
+ [lines, &limitIfaces, &limitTag, &limitUid, &unknownIfaceBytesTotal, &ifaceMap](
+ const StatsKey& key,
+ const BpfMap<StatsKey, StatsValue>& statsMap) -> Result<void> {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key,
+ &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ std::string ifnameStr(ifname);
+ if (limitIfaces.size() > 0 &&
+ std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) {
+ // Nothing matched; skip this line.
+ return Result<void>();
+ }
+ if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) {
+ return Result<void>();
+ }
+ if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) {
+ return Result<void>();
+ }
+ Result<StatsValue> 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<void>();
+ };
+ Result<void> 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<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid) {
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+
+ BpfMapRO<uint32_t, uint8_t> 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<StatsKey, StatsValue> 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<void> 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<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ const auto processDetailIfaceStats = [lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
+ const uint32_t& key, const StatsValue& value,
+ const BpfMap<uint32_t, StatsValue>&) {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ 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<void>();
+ };
+ Result<void> 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<stats_line>* lines) {
+ int ret = 0;
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+
+ BpfMapRO<uint32_t, StatsValue> 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<stats_line>* 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 0000000..fb8f0ec
--- /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 <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include <netdutils/MockSyscalls.h>
+#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<uint64_t, UidTagValue> mFakeCookieTagMap;
+ BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMap;
+ BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
+ BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
+
+ void SetUp() {
+ ASSERT_EQ(0, setrlimitForTest());
+
+ mFakeCookieTagMap = BpfMap<uint64_t, UidTagValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeCookieTagMap.getMap());
+
+ mFakeAppUidStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
+
+ mFakeStatsMap = BpfMap<StatsKey, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeStatsMap.getMap());
+
+ mFakeIfaceIndexNameMap = BpfMap<uint32_t, IfaceValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeIfaceIndexNameMap.getMap());
+
+ mFakeIfaceStatsMap = BpfMap<uint32_t, StatsValue>(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<StatsKey, StatsValue>& 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<uint64_t, UidTagValue>&) {
+ EXPECT_GE((uint64_t)5, key);
+ totalCount++;
+ totalSum += key;
+ return Result<void>();
+ };
+ 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<stats_line> lines;
+ std::vector<std::string> 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<stats_line> lines;
+ std::vector<std::string> 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<stats_line> lines;
+ std::vector<std::string> 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<stats_line> lines;
+ std::vector<std::string> 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<stats_line> 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<stats_line> lines;
+ std::vector<std::string> 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<stats_line> lines;
+ std::vector<std::string> 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 0000000..8ab7e25
--- /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 <bpf/BpfMap.h>
+#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<uint32_t, StatsValue>& appUidStatsMap);
+// For test only
+int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
+// For test only
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+// For test only
+int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
+// For test only
+template <class Key>
+int getIfaceNameFromMap(const BpfMap<uint32_t, IfaceValue>& ifaceMap,
+ const BpfMap<Key, StatsValue>& 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 <class Key>
+void maybeLogUnknownIface(int ifaceIndex, const BpfMap<Key, StatsValue>& 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<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+
+int bpfGetUidStats(uid_t uid, Stats* stats);
+int bpfGetIfaceStats(const char* iface, Stats* stats);
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid);
+
+int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>* lines);
+int cleanStatsMap();
+} // namespace bpf
+} // namespace android
+
+#endif // _BPF_NETWORKSTATS_H