Merge changes from topic "trafficctrl_coverage"
* changes:
Extend unit test for dump function
Add unit test for getFirewallType
Add unit test for dump function
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index dfa7097..ff2ebad 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -30,6 +30,8 @@
#include <gtest/gtest.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <binder/Status.h>
@@ -49,6 +51,7 @@
using android::netdutils::Status;
using base::Result;
using netdutils::isOk;
+using netdutils::statusFromErrno;
constexpr int TEST_MAP_SIZE = 10;
constexpr uid_t TEST_UID = 10086;
@@ -56,6 +59,13 @@
constexpr uid_t TEST_UID3 = 98765;
constexpr uint32_t TEST_TAG = 42;
constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr int TEST_COOKIE = 1;
+constexpr char TEST_IFNAME[] = "test0";
+constexpr int TEST_IFINDEX = 999;
+constexpr int RXPACKETS = 1;
+constexpr int RXBYTES = 100;
+constexpr int TXPACKETS = 0;
+constexpr int TXBYTES = 0;
#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
@@ -69,6 +79,8 @@
BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
+ BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
void SetUp() {
std::lock_guard guard(mTc.mMutex);
@@ -91,6 +103,12 @@
mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidPermissionMap);
+ mFakeUidCounterSetMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeUidCounterSetMap);
+
+ mFakeIfaceIndexNameMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeIfaceIndexNameMap);
+
mTc.mCookieTagMap = mFakeCookieTagMap;
ASSERT_VALID(mTc.mCookieTagMap);
mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
@@ -108,21 +126,38 @@
mTc.mUidPermissionMap = mFakeUidPermissionMap;
ASSERT_VALID(mTc.mUidPermissionMap);
mTc.mPrivilegedUser.clear();
+
+ mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+ ASSERT_VALID(mTc.mUidCounterSetMap);
+
+ mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+ ASSERT_VALID(mTc.mIfaceIndexNameMap);
}
void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
- *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
- StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
- EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
- key->tag = 0;
+ *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = TEST_IFINDEX};
+ StatsValue statsMapValue = {.rxPackets = RXPACKETS, .rxBytes = RXBYTES,
+ .txPackets = TXPACKETS, .txBytes = TXBYTES};
EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
EXPECT_RESULT_OK(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY));
// put tag information back to statsKey
key->tag = tag;
}
+ void populateFakeCounterSet(uint32_t uid, uint32_t counterSet) {
+ EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
+ }
+
+ void populateFakeIfaceIndexName(const char* name, uint32_t ifaceIndex) {
+ if (name == nullptr || ifaceIndex <= 0) return;
+
+ IfaceValue iface;
+ strlcpy(iface.name, name, sizeof(IfaceValue));
+ EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+ }
+
void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
uint32_t uid = TEST_UID;
EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST));
@@ -242,17 +277,17 @@
EXPECT_EQ(tag, cookieMapResult.value().tag);
Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
tagStatsMapKey.tag = 0;
statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
EXPECT_RESULT_OK(appStatsResult);
- EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+ EXPECT_EQ((uint64_t)RXPACKETS, appStatsResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)RXBYTES, appStatsResult.value().rxBytes);
}
Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
@@ -265,6 +300,62 @@
return ret;
}
+ Status dump(bool verbose, std::vector<std::string>& outputLines) {
+ if (!outputLines.empty()) return statusFromErrno(EUCLEAN, "Output buffer is not empty");
+
+ android::base::unique_fd localFd, remoteFd;
+ if (!Pipe(&localFd, &remoteFd)) return statusFromErrno(errno, "Failed on pipe");
+
+ // dump() blocks until another thread has consumed all its output.
+ std::thread dumpThread =
+ std::thread([this, remoteFd{std::move(remoteFd)}, verbose]() {
+ mTc.dump(remoteFd, verbose);
+ });
+
+ std::string dumpContent;
+ if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+ return statusFromErrno(errno, "Failed to read dump results from fd");
+ }
+ dumpThread.join();
+
+ std::stringstream dumpStream(std::move(dumpContent));
+ std::string line;
+ while (std::getline(dumpStream, line)) {
+ outputLines.push_back(line);
+ }
+
+ return netdutils::status::ok;
+ }
+
+ // Strings in the |expect| must exist in dump results in order. But no need to be consecutive.
+ bool expectDumpsysContains(std::vector<std::string>& expect) {
+ if (expect.empty()) return false;
+
+ std::vector<std::string> output;
+ Status result = dump(true, output);
+ if (!isOk(result)) {
+ GTEST_LOG_(ERROR) << "TrafficController dump failed: " << netdutils::toString(result);
+ return false;
+ }
+
+ int matched = 0;
+ auto it = expect.begin();
+ for (const auto& line : output) {
+ if (it == expect.end()) break;
+ if (std::string::npos != line.find(*it)) {
+ matched++;
+ ++it;
+ }
+ }
+
+ if (matched != expect.size()) {
+ // dump results for debugging
+ for (const auto& o : output) LOG(INFO) << "output: " << o;
+ for (const auto& e : expect) LOG(INFO) << "expect: " << e;
+ return false;
+ }
+ return true;
+ }
};
TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
@@ -645,6 +736,83 @@
expectPrivilegedUserSetEmpty();
}
+TEST_F(TrafficControllerTest, TestDumpsys) {
+ StatsKey tagStatsMapKey;
+ populateFakeStats(TEST_COOKIE, TEST_UID, TEST_TAG, &tagStatsMapKey);
+ populateFakeCounterSet(TEST_UID3, TEST_COUNTERSET);
+
+ // Expect: (part of this depends on hard-code values in populateFakeStats())
+ //
+ // mCookieTagMap:
+ // cookie=1 tag=0x2a uid=10086
+ //
+ // mUidCounterSetMap:
+ // 98765 1
+ //
+ // mAppUidStatsMap::
+ // uid rxBytes rxPackets txBytes txPackets
+ // 10086 100 1 0 0
+ //
+ // mStatsMapA:
+ // ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets
+ // 999 test0 0x2a 10086 1 100 1 0 0
+ std::vector<std::string> expectedLines = {
+ "mCookieTagMap:",
+ fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID),
+ "mUidCounterSetMap:",
+ fmt::format("{} {}", TEST_UID3, TEST_COUNTERSET),
+ "mAppUidStatsMap::", // TODO@: fix double colon
+ "uid rxBytes rxPackets txBytes txPackets",
+ fmt::format("{} {} {} {} {}", TEST_UID, RXBYTES, RXPACKETS, TXBYTES, TXPACKETS),
+ "mStatsMapA",
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
+ fmt::format("{} {} {:#x} {} {} {} {} {} {}",
+ TEST_IFINDEX, TEST_IFNAME, TEST_TAG, TEST_UID, TEST_COUNTERSET, RXBYTES,
+ RXPACKETS, TXBYTES, TXPACKETS)};
+
+ populateFakeIfaceIndexName(TEST_IFNAME, TEST_IFINDEX);
+ expectedLines.emplace_back("mIfaceIndexNameMap:");
+ expectedLines.emplace_back(fmt::format("ifaceIndex={} ifaceName={}",
+ TEST_IFINDEX, TEST_IFNAME));
+
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectedLines.emplace_back("mUidOwnerMap:");
+ expectedLines.emplace_back(fmt::format("{} HAPPY_BOX_MATCH", TEST_UID));
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, {TEST_UID2});
+ expectedLines.emplace_back("mUidPermissionMap:");
+ expectedLines.emplace_back(fmt::format("{} BPF_PERMISSION_UPDATE_DEVICE_STATS", TEST_UID2));
+ expectedLines.emplace_back("mPrivilegedUser:");
+ expectedLines.emplace_back(fmt::format("{} ALLOW_UPDATE_DEVICE_STATS", TEST_UID2));
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, getFirewallType) {
+ static const struct TestConfig {
+ ChildChain childChain;
+ FirewallType firewallType;
+ } testConfigs[] = {
+ // clang-format off
+ {NONE, DENYLIST},
+ {DOZABLE, ALLOWLIST},
+ {STANDBY, DENYLIST},
+ {POWERSAVE, ALLOWLIST},
+ {RESTRICTED, ALLOWLIST},
+ {LOW_POWER_STANDBY, ALLOWLIST},
+ {LOCKDOWN, DENYLIST},
+ {OEM_DENY_1, DENYLIST},
+ {OEM_DENY_2, DENYLIST},
+ {INVALID_CHAIN, DENYLIST},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.childChain, config.firewallType));
+ EXPECT_EQ(config.firewallType, mTc.getFirewallType(config.childChain));
+ }
+}
+
constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;