Merge changes Ie826477d,I5125a3ac
* changes:
[CLATJ#26] ClatCoordinator: reword clatd starting failure logging
[CLATJ#25] ClatCoordinator: stop clatd process gracefully
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ffbd1fc..780ba26 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -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": [
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index a3ca90c..fa5af49 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -45,9 +45,8 @@
"//packages/modules/Connectivity/service/native/libs/libclat",
"//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",
],
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 8577d9d..f0df97b 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -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.
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 72ee431..f0af8b4 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -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"
diff --git a/framework/api/current.txt b/framework/api/current.txt
index ce0c868..a373b71 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -326,6 +326,8 @@
field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+ field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23
+ field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
field public static final int NET_CAPABILITY_RCS = 8; // 0x8
field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index c98be47..4ae3a06 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -357,6 +357,8 @@
NET_CAPABILITY_BIP,
NET_CAPABILITY_HEAD_UNIT,
NET_CAPABILITY_MMTEL,
+ NET_CAPABILITY_PRIORITIZE_LATENCY,
+ NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
})
public @interface NetCapability { }
@@ -600,8 +602,18 @@
*/
public static final int NET_CAPABILITY_MMTEL = 33;
+ /**
+ * Indicates that this network should be able to prioritize latency for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
+
+ /**
+ * Indicates that this network should be able to prioritize bandwidth for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_MMTEL;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -2241,6 +2253,8 @@
case NET_CAPABILITY_BIP: return "BIP";
case NET_CAPABILITY_HEAD_UNIT: return "HEAD_UNIT";
case NET_CAPABILITY_MMTEL: return "MMTEL";
+ case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
+ case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
default: return Integer.toString(capability);
}
}
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
new file mode 100644
index 0000000..bceeefa
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -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",
+ ],
+}
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
new file mode 100644
index 0000000..4d605ce
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <net/if.h>
+#include <string.h>
+#include <unordered_set>
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "android-base/file.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#define LOG_TAG "BpfNetworkStats"
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+
+// The target map for stats reading should be the inactive map, which is opposite
+// 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 than rhs, only compare iface, uid, tag and set.
+bool operator<(const stats_line& lhs, const stats_line& rhs) {
+ int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
+ if (ret != 0) return ret < 0;
+ if (lhs.uid < rhs.uid) return true;
+ if (lhs.uid > rhs.uid) return false;
+ if (lhs.tag < rhs.tag) return true;
+ if (lhs.tag > rhs.tag) return false;
+ if (lhs.set < rhs.set) return true;
+ if (lhs.set > rhs.set) return false;
+ return false;
+}
+
+stats_line& stats_line::operator=(const stats_line& rhs) {
+ if (this == &rhs) return *this;
+
+ strlcpy(iface, rhs.iface, sizeof(iface));
+ uid = rhs.uid;
+ set = rhs.set;
+ tag = rhs.tag;
+ rxPackets = rhs.rxPackets;
+ txPackets = rhs.txPackets;
+ rxBytes = rhs.rxBytes;
+ txBytes = rhs.txBytes;
+ return *this;
+}
+
+stats_line& stats_line::operator+=(const stats_line& rhs) {
+ rxPackets += rhs.rxPackets;
+ txPackets += rhs.txPackets;
+ rxBytes += rhs.rxBytes;
+ txBytes += rhs.txBytes;
+ return *this;
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
new file mode 100644
index 0000000..4974b96
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -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 than 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, TestUnknownIfaceError) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0 * 20,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1 * 20,
+ };
+ uint32_t ifaceIndex = UNKNOWN_IFACE;
+ populateFakeStats(TEST_UID1, 0, ifaceIndex, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0 * 40,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1 * 40,
+ };
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+ StatsKey curKey = {
+ .uid = TEST_UID1,
+ .tag = 0,
+ .counterSet = TEST_COUNTERSET0,
+ .ifaceIndex = ifaceIndex,
+ };
+ char ifname[IFNAMSIZ];
+ int64_t unknownIfaceBytesTotal = 0;
+ ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
+ ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal);
+ curKey.ifaceIndex = IFACE_INDEX2;
+ ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
+ ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(-1, unknownIfaceBytesTotal);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ // TODO: find a way to test the total of unknown Iface Bytes go above limit.
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsDetail) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ updateIfaceMap(LONG_IFACE_NAME, IFACE_INDEX4);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ uint32_t ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX4;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ std::vector<stats_line> lines;
+ ASSERT_EQ(0,
+ parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+
+ expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+ expectStatsLineEqual(value1, IFACE_NAME3, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+ expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[2]);
+ ASSERT_EQ(0, strcmp(TRUNCATED_IFACE_NAME, lines[3].iface));
+ expectStatsLineEqual(value2, TRUNCATED_IFACE_NAME, UID_ALL, SET_ALL, TAG_NONE, lines[3]);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
+ // Create iface indexes with duplicate iface name.
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX3); // Duplicate!
+
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ StatsValue value3 = {
+ .rxPackets = TEST_PACKET0 * 2,
+ .rxBytes = TEST_BYTES0 * 2,
+ .txPackets = TEST_PACKET1 * 2,
+ .txBytes = TEST_BYTES1 * 2,
+ };
+
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+
+ // Test empty stats.
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 0, lines.size());
+ lines.clear();
+
+ // Test 1 line stats.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+ lines.clear();
+
+ // These items should not be grouped.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET1, value2, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
+ mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 5, lines.size());
+ lines.clear();
+
+ // These items should be grouped.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 5, lines.size());
+
+ // Verify Sorted & Grouped.
+ expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+ expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]);
+ expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]);
+ expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]);
+ expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]);
+ lines.clear();
+
+ // Perform test on IfaceStats.
+ uint32_t ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ // This should be grouped.
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ ASSERT_EQ(0,
+ parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 2, lines.size());
+
+ expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+ expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+ lines.clear();
+}
+
+// Test to verify that subtract overflow will not be triggered by the compare function invoked from
+// sorting. See http:/b/119193941.
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+
+ // Mutate uid, 0 < TEST_UID1 < INT_MAX < INT_MIN < UINT_MAX.
+ populateFakeStats(0, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(UINT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(INT_MIN, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(INT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ // Mutate tag, 0 < TEST_TAG < INT_MAX < INT_MIN < UINT_MAX.
+ populateFakeStats(TEST_UID1, INT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, INT_MIN, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, UINT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
+
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 8, lines.size());
+
+ // Uid 0 first
+ expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+
+ // Test uid, mutate tag.
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[1]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[2]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[3]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[4]);
+
+ // Mutate uid.
+ expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[5]);
+ expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[6]);
+ expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]);
+ lines.clear();
+}
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
new file mode 100644
index 0000000..8ab7e25
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BPF_NETWORKSTATS_H
+#define _BPF_NETWORKSTATS_H
+
+#include <bpf/BpfMap.h>
+#include "bpf_shared.h"
+
+namespace android {
+namespace bpf {
+
+// TODO: set this to a proper value based on the map size;
+constexpr int TAG_STATS_MAP_SOFT_LIMIT = 3;
+constexpr int UID_ALL = -1;
+constexpr int TAG_ALL = -1;
+constexpr int TAG_NONE = 0;
+constexpr int SET_ALL = -1;
+constexpr int SET_DEFAULT = 0;
+constexpr int SET_FOREGROUND = 1;
+
+// The limit for stats received by a unknown interface;
+constexpr const int64_t MAX_UNKNOWN_IFACE_BYTES = 100 * 1000;
+
+// This is used by
+// frameworks/base/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+// make sure it is consistent with the JNI code before changing this.
+struct stats_line {
+ char iface[32];
+ uint32_t uid;
+ uint32_t set;
+ uint32_t tag;
+ int64_t rxBytes;
+ int64_t rxPackets;
+ int64_t txBytes;
+ int64_t txPackets;
+
+ stats_line& operator=(const stats_line& rhs);
+ stats_line& operator+=(const stats_line& rhs);
+};
+
+bool operator==(const stats_line& lhs, const stats_line& rhs);
+bool operator<(const stats_line& lhs, const stats_line& rhs);
+
+// For test only
+int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& appUidStatsMap);
+// For test only
+int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
+// For test only
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+// For test only
+int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
+// For test only
+template <class Key>
+int getIfaceNameFromMap(const BpfMap<uint32_t, IfaceValue>& ifaceMap,
+ const BpfMap<Key, StatsValue>& statsMap, uint32_t ifaceIndex, char* ifname,
+ const Key& curKey, int64_t* unknownIfaceBytesTotal) {
+ auto iface = ifaceMap.readValue(ifaceIndex);
+ if (!iface.ok()) {
+ maybeLogUnknownIface(ifaceIndex, statsMap, curKey, unknownIfaceBytesTotal);
+ return -ENODEV;
+ }
+ strlcpy(ifname, iface.value().name, sizeof(IfaceValue));
+ return 0;
+}
+
+template <class Key>
+void maybeLogUnknownIface(int ifaceIndex, const BpfMap<Key, StatsValue>& statsMap,
+ const Key& curKey, int64_t* unknownIfaceBytesTotal) {
+ // Have we already logged an error?
+ if (*unknownIfaceBytesTotal == -1) {
+ return;
+ }
+
+ // Are we undercounting enough data to be worth logging?
+ auto statsEntry = statsMap.readValue(curKey);
+ if (!statsEntry.ok()) {
+ // No data is being undercounted.
+ return;
+ }
+
+ *unknownIfaceBytesTotal += (statsEntry.value().rxBytes + statsEntry.value().txBytes);
+ if (*unknownIfaceBytesTotal >= MAX_UNKNOWN_IFACE_BYTES) {
+ ALOGE("Unknown name for ifindex %d with more than %" PRId64 " bytes of traffic", ifaceIndex,
+ *unknownIfaceBytesTotal);
+ *unknownIfaceBytesTotal = -1;
+ }
+}
+
+// For test only
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+
+int bpfGetUidStats(uid_t uid, Stats* stats);
+int bpfGetIfaceStats(const char* iface, Stats* stats);
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid);
+
+int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>* lines);
+int cleanStatsMap();
+} // namespace bpf
+} // namespace android
+
+#endif // _BPF_NETWORKSTATS_H
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index cac545d..97fcc43 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -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>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ae6fe11..145eade 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -4046,7 +4046,7 @@
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.VIRTUAL,
INetd.PERMISSION_NONE,
(nai.networkAgentConfig == null || !nai.networkAgentConfig.allowBypass),
- getVpnType(nai), /*excludeLocalRoutes=*/ false);
+ getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities), /*secure=*/ false,
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index d03629d..bea00a9 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -34,6 +34,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
@@ -415,6 +417,31 @@
assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
}
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testPrioritizeLatencyAndBandwidth() {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void testOemPrivate() {
NetworkCapabilities nc = new NetworkCapabilities();
diff --git a/tests/cts/net/native/Android.bp b/tests/cts/net/native/Android.bp
index 1d1c18e..153ff51 100644
--- a/tests/cts/net/native/Android.bp
+++ b/tests/cts/net/native/Android.bp
@@ -43,6 +43,7 @@
static_libs: [
"libbpf_android",
"libgtest",
+ "libmodules-utils-build",
],
// Tag this module as a cts test artifact
diff --git a/tests/cts/net/native/src/BpfCompatTest.cpp b/tests/cts/net/native/src/BpfCompatTest.cpp
index 874bad4..97ecb9e 100644
--- a/tests/cts/net/native/src/BpfCompatTest.cpp
+++ b/tests/cts/net/native/src/BpfCompatTest.cpp
@@ -21,6 +21,8 @@
#include <gtest/gtest.h>
+#include "android-modules-utils/sdk_level.h"
+
#include "libbpf_android.h"
using namespace android::bpf;
@@ -33,11 +35,17 @@
EXPECT_EQ(28, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
}
-TEST(BpfTest, bpfStructSizeTest) {
+TEST(BpfTest, bpfStructSizeTestPreT) {
+ if (android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << "T+ device.";
doBpfStructSizeTest("/system/etc/bpf/netd.o");
doBpfStructSizeTest("/system/etc/bpf/clatd.o");
}
+TEST(BpfTest, bpfStructSizeTest) {
+ doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o");
+ doBpfStructSizeTest("/system/etc/bpf/time_in_state.o");
+}
+
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 80c2db4..53e4ab7 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -168,6 +168,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -3021,6 +3022,13 @@
}
}
+ @Test
+ public void testDump() throws Exception {
+ final String dumpOutput = DumpTestUtils.dumpServiceWithShellPermission(
+ Context.CONNECTIVITY_SERVICE, "--short");
+ assertTrue(dumpOutput, dumpOutput.contains("Active default network"));
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 0bef0e2..2b698fd 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -38,8 +38,8 @@
"liblog",
"liblzma",
"libnativehelper",
- "libnetdbpf",
"libnetdutils",
+ "libnetworkstats",
"libnetworkstatsfactorytestjni",
"libpackagelistparser",
"libpcre2",
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index ec0420e..1b512f0 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -171,7 +171,7 @@
fun testBuilder_ratType() {
// Assert illegal ratTypes cannot make an identity.
listOf(Integer.MIN_VALUE, NetworkTemplate.NETWORK_TYPE_ALL,
- TelephonyManager.NETWORK_TYPE_UNKNOWN - 1, Integer.MAX_VALUE)
+ NetworkTemplate.NETWORK_TYPE_5G_NSA - 1, Integer.MAX_VALUE)
.forEach {
assertFailsWith<IllegalArgumentException> {
NetworkIdentity.Builder()
@@ -184,6 +184,7 @@
// Verify legitimate ratTypes can make an identity.
TelephonyManager.getAllNetworkTypes().toMutableList().also {
it.add(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+ it.add(NetworkTemplate.NETWORK_TYPE_5G_NSA)
}.forEach { rat ->
NetworkIdentity.Builder()
.setType(TYPE_MOBILE)
@@ -199,11 +200,11 @@
// Assert illegal oemManage values cannot make an identity.
listOf(Integer.MIN_VALUE, NetworkTemplate.OEM_MANAGED_ALL, NetworkTemplate.OEM_MANAGED_YES,
Integer.MAX_VALUE)
- .forEach {
+ .forEach { oemManaged ->
assertFailsWith<IllegalArgumentException> {
NetworkIdentity.Builder()
.setType(TYPE_MOBILE)
- .setRatType(it)
+ .setOemManaged(oemManaged)
.build()
}
}
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 2e82986..c27ee93 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -38,11 +38,13 @@
import static org.junit.Assert.fail;
import android.content.res.Resources;
+import android.net.NetworkStatsCollection.Key;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.RecurrenceRule;
import androidx.test.InstrumentationRegistry;
@@ -73,6 +75,7 @@
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Tests for {@link NetworkStatsCollection}.
@@ -530,6 +533,52 @@
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
}
+ @Test
+ public void testBuilder() {
+ final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final NetworkIdentitySet ident = new NetworkIdentitySet();
+ final Key key1 = new Key(ident, 0, 0, 0);
+ final Key key2 = new Key(ident, 1, 0, 0);
+ final long bucketDuration = 10;
+
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 10, 3,
+ 41, 7, 1, 0);
+
+ NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .build();
+
+ NetworkStatsHistory history2 = new NetworkStatsHistory(10, 5);
+
+ NetworkStatsCollection actualCollection = new NetworkStatsCollection.Builder(bucketDuration)
+ .addEntry(key1, history1)
+ .addEntry(key2, history2)
+ .build();
+
+ // The builder will omit any entry with empty history. Thus, history2
+ // is not expected in the result collection.
+ expectedEntries.put(key1, history1);
+
+ final Map<Key, NetworkStatsHistory> actualEntries = actualCollection.getEntries();
+
+ assertEquals(expectedEntries.size(), actualEntries.size());
+ for (Key expectedKey : expectedEntries.keySet()) {
+ final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+
+ final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+ assertNotNull(actualHistory);
+
+ assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+
+ actualEntries.remove(expectedKey);
+ }
+ assertEquals(0, actualEntries.size());
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
@@ -587,6 +636,14 @@
actual.txBytes, actual.txPackets, 0L));
}
+ private static void assertEntry(NetworkStatsHistory.Entry expected,
+ NetworkStatsHistory.Entry actual) {
+ assertEntry(new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L),
+ new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L));
+ }
+
private static void assertEntry(NetworkStats.Entry expected,
NetworkStats.Entry actual) {
assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes);
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 6a7da9e..66dcf6d 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -42,7 +42,6 @@
import android.net.NetworkStats;
import android.net.NetworkStatsAccess;
import android.net.NetworkTemplate;
-import android.net.netstats.IUsageCallback;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
@@ -101,7 +100,7 @@
private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
@Mock private IBinder mUsageCallbackBinder;
- @Mock private IUsageCallback mUsageCallback;
+ private TestableUsageCallback mUsageCallback;
@Before
public void setUp() throws Exception {
@@ -119,20 +118,27 @@
mActiveIfaces = new ArrayMap<>();
mActiveUidIfaces = new ArrayMap<>();
- Mockito.when(mUsageCallback.asBinder()).thenReturn(mUsageCallbackBinder);
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@Test
public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
- long thresholdTooLowBytes = 1L;
- DataUsageRequest inputRequest = new DataUsageRequest(
+ final long thresholdTooLowBytes = 1L;
+ final DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ final DataUsageRequest requestByApp = mStatsObservers.register(inputRequest, mUsageCallback,
+ UID_RED, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestByApp.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
+ assertEquals(THRESHOLD_BYTES, requestByApp.thresholdInBytes);
+
+ // Verify the threshold requested by system uid won't be overridden.
+ final DataUsageRequest requestBySystem = mStatsObservers.register(inputRequest,
+ mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestBySystem.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
+ assertEquals(1, requestBySystem.thresholdInBytes);
}
@Test
@@ -304,7 +310,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- Mockito.verify(mUsageCallback).onThresholdReached(any());
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -337,7 +343,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- Mockito.verify(mUsageCallback).onThresholdReached(any());
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -402,7 +408,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- Mockito.verify(mUsageCallback).onThresholdReached(any());
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 94a4f3d..ea35c31 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -1292,7 +1292,7 @@
// Wait for the caller to invoke expectOnThresholdReached.
- mUsageCallback.expectOnThresholdReached();
+ mUsageCallback.expectOnThresholdReached(request);
// Allow binder to disconnect
when(mUsageCallbackBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()))
@@ -1302,7 +1302,7 @@
mService.unregisterUsageRequest(request);
// Wait for the caller to invoke expectOnCallbackReleased.
- mUsageCallback.expectOnCallbackReleased();
+ mUsageCallback.expectOnCallbackReleased(request);
// Make sure that the caller binder gets disconnected
verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
diff --git a/tests/unit/java/com/android/server/net/TestableUsageCallback.kt b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
index 44f588c..1917ec3 100644
--- a/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
+++ b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
@@ -21,37 +21,34 @@
import android.os.IBinder
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit
-import kotlin.test.assertEquals
import kotlin.test.fail
private const val DEFAULT_TIMEOUT_MS = 200L
// TODO: Move the class to static libs once all downstream have IUsageCallback definition.
-open class TestableUsageCallback(private val binder: IBinder) : IUsageCallback.Stub() {
- sealed class CallbackType {
- object OnThresholdReached : CallbackType()
- object OnCallbackReleased : CallbackType()
+class TestableUsageCallback(private val binder: IBinder) : IUsageCallback.Stub() {
+ sealed class CallbackType(val request: DataUsageRequest) {
+ class OnThresholdReached(request: DataUsageRequest) : CallbackType(request)
+ class OnCallbackReleased(request: DataUsageRequest) : CallbackType(request)
}
// TODO: Change to use ArrayTrackRecord once moved into to the module.
private val history = LinkedBlockingQueue<CallbackType>()
override fun onThresholdReached(request: DataUsageRequest) {
- history.add(CallbackType.OnThresholdReached)
+ history.add(CallbackType.OnThresholdReached(request))
}
override fun onCallbackReleased(request: DataUsageRequest) {
- history.add(CallbackType.OnCallbackReleased)
+ history.add(CallbackType.OnCallbackReleased(request))
}
- fun expectOnThresholdReached() {
- assertEquals(CallbackType.OnThresholdReached,
- history.poll(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+ fun expectOnThresholdReached(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnThresholdReached>(request, DEFAULT_TIMEOUT_MS)
}
- fun expectOnCallbackReleased() {
- assertEquals(CallbackType.OnCallbackReleased,
- history.poll(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+ fun expectOnCallbackReleased(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnCallbackReleased>(request, DEFAULT_TIMEOUT_MS)
}
@JvmOverloads
@@ -60,6 +57,22 @@
cb?.let { fail("Expected no callback but got $cb") }
}
+ // Expects a callback of the specified request on the specified network within the timeout.
+ // If no callback arrives, or a different callback arrives, fail.
+ private inline fun <reified T : CallbackType> expectCallback(
+ expectedRequest: DataUsageRequest,
+ timeoutMs: Long
+ ) {
+ history.poll(timeoutMs, TimeUnit.MILLISECONDS).let {
+ if (it !is T || it.request != expectedRequest) {
+ fail("Unexpected callback : $it," +
+ " expected ${T::class} with Request[$expectedRequest]")
+ } else {
+ it
+ }
+ }
+ }
+
override fun asBinder(): IBinder {
return binder
}
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index fe971e7..0f71c13 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -24,7 +24,7 @@
"libbpf_android",
"liblog",
"libnativehelper",
- "libnetdbpf",
"libnetdutils",
+ "libnetworkstats",
],
}