Rename libnetdbpf to libnetworkstats

Two reason for renaming:
1. Avoid module name collision in sc-mainline-prod branch.
2. The libnetdbpf was misnamed before.

Bug: 202086915
Test: atest libnetworkstats_test FrameworksNetTests
      ConnectivityCoverageTests FrameworksNetSmokeTests
      CtsAppOpsTestCases
Change-Id: I87fcf4b1a9d58780a45743a9aa91b9b936e54266
This commit is contained in:
Ken Chen 2022-01-23 15:39:13 +08:00
commit f426b2b287
11 changed files with 37 additions and 21 deletions

View file

@ -26,6 +26,9 @@
},
{
"name": "traffic_controller_unit_test"
},
{
"name": "libnetworkstats_test"
}
],
"postsubmit": [
@ -42,6 +45,9 @@
{
"name": "traffic_controller_unit_test",
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
"name": "libnetworkstats_test"
}
],
"mainline-presubmit": [
@ -61,6 +67,9 @@
},
{
"name": "traffic_controller_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
"name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"mainline-postsubmit": [

View file

@ -44,9 +44,8 @@ cc_library_headers {
"//frameworks/base/services/core/jni",
"//packages/modules/Connectivity/Tethering",
"//packages/modules/Connectivity/service/native",
"//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
"//packages/modules/Connectivity/tests/unit/jni",
// TODO: remove system/netd/* when all BPF code is moved out of Netd.
"//system/netd/libnetdbpf",
"//system/netd/server",
"//system/netd/tests",
],

View file

@ -20,7 +20,6 @@
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <netdutils/UidConstants.h>
// This header file is shared by eBPF kernel programs (C) and netd (C++) and
// some of the maps are also accessed directly from Java mainline module code.

View file

@ -25,6 +25,7 @@
#include <linux/ipv6.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <netdutils/UidConstants.h>
#include <stdbool.h>
#include <stdint.h>
#include "bpf_net_helpers.h"

View file

@ -0,0 +1,71 @@
//
// 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 {
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_library {
name: "libnetworkstats",
vendor_available: false,
host_supported: false,
header_libs: ["bpf_connectivity_headers"],
srcs: [
"BpfNetworkStats.cpp"
],
shared_libs: [
"libbase",
"liblog",
],
export_include_dirs: ["include"],
cflags: [
"-Wall",
"-Werror",
"-Wno-unused-parameter",
"-Wthread-safety",
],
sanitize: {
cfi: true,
},
apex_available: [
"//apex_available:platform",
"com.android.tethering",
],
min_sdk_version: "30",
}
cc_test {
name: "libnetworkstats_test",
test_suites: ["general-tests"],
require_root: true, // required by setrlimitForTest()
header_libs: ["bpf_connectivity_headers"],
srcs: [
"BpfNetworkStatsTest.cpp",
],
cflags: [
"-Wall",
"-Werror",
"-Wno-unused-parameter",
"-Wthread-safety",
],
static_libs: ["libgmock"],
shared_libs: [
"libbase",
"liblog",
"libnetworkstats",
"libutils",
],
}

View file

@ -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

View file

@ -0,0 +1,569 @@
/*
* 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 "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

View file

@ -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

View file

@ -40,6 +40,7 @@
#include <android-base/unique_fd.h>
#include <netdutils/StatusOr.h>
#include <netdutils/Syscalls.h>
#include <netdutils/UidConstants.h>
#include <netdutils/Utils.h>
#include <private/android_filesystem_config.h>

View file

@ -38,8 +38,8 @@ java_defaults {
"liblog",
"liblzma",
"libnativehelper",
"libnetdbpf",
"libnetdutils",
"libnetworkstats",
"libnetworkstatsfactorytestjni",
"libpackagelistparser",
"libpcre2",

View file

@ -24,7 +24,7 @@ cc_library_shared {
"libbpf_android",
"liblog",
"libnativehelper",
"libnetdbpf",
"libnetdutils",
"libnetworkstats",
],
}