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:
parent
f2bb7b1ae8
commit
f426b2b287
11 changed files with 37 additions and 21 deletions
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
71
service-t/native/libs/libnetworkstats/Android.bp
Normal file
71
service-t/native/libs/libnetworkstats/Android.bp
Normal 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",
|
||||
],
|
||||
}
|
||||
350
service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
Normal file
350
service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
Normal 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
|
||||
569
service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
Normal file
569
service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ java_defaults {
|
|||
"liblog",
|
||||
"liblzma",
|
||||
"libnativehelper",
|
||||
"libnetdbpf",
|
||||
"libnetdutils",
|
||||
"libnetworkstats",
|
||||
"libnetworkstatsfactorytestjni",
|
||||
"libpackagelistparser",
|
||||
"libpcre2",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ cc_library_shared {
|
|||
"libbpf_android",
|
||||
"liblog",
|
||||
"libnativehelper",
|
||||
"libnetdbpf",
|
||||
"libnetdutils",
|
||||
"libnetworkstats",
|
||||
],
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue