bpf network stats - move double accounting tag!=0 out of bpf

Instead of also accounting tag!=0 traffic against tag==0 slot,
while the bpf code writes into the map, move this logic into
the userspace jni code which reads from the map.

Simplifies the bpf program making things easier on the
kernel's bpf verifier, and is better for performance,
since a per-packet fixup operation becomes a per-poll fixup.

Test: TreeHugger, atest libnetworkstats_test FrameworksNetTests
Bug: 276296921
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: Ic220a201781a1170bcffe327fe5664fc12b65dd9
This commit is contained in:
Maciej Żenczykowski 2023-04-12 05:13:54 +00:00
commit ce7431f1fc
3 changed files with 64 additions and 44 deletions

View file

@ -446,11 +446,6 @@ static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, boo
return match;
}
if (key.tag) {
update_stats_with_config(skb, egress, &key, *selectedMap);
key.tag = 0;
}
do_packet_tracing(skb, egress, uid, tag, enable_tracing, kver);
update_stats_with_config(skb, egress, &key, *selectedMap);
update_app_uid_stats_map(skb, egress, &uid);

View file

@ -126,7 +126,13 @@ int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
if (!statsEntry.ok()) {
return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
}
lines->push_back(populateStatsEntry(key, statsEntry.value(), ifname));
stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname);
lines->push_back(newLine);
if (newLine.tag) {
// account tagged traffic in the untagged stats (for historical reasons?)
newLine.tag = 0;
lines->push_back(newLine);
}
return Result<void>();
};
Result<void> res = statsMap.iterate(processDetailUidStats);
@ -236,21 +242,20 @@ void groupNetworkStats(std::vector<stats_line>* lines) {
std::sort(lines->begin(), lines->end());
// Similar to std::unique(), but aggregates the duplicates rather than discarding them.
size_t nextOutput = 0;
size_t currentOutput = 0;
for (size_t i = 1; i < lines->size(); i++) {
if (lines->at(nextOutput) == lines->at(i)) {
lines->at(nextOutput) += lines->at(i);
// note that == operator only compares the 'key' portion: iface/uid/tag/set
if (lines->at(currentOutput) == lines->at(i)) {
// while += operator only affects the 'data' portion: {rx,tx}{Bytes,Packets}
lines->at(currentOutput) += lines->at(i);
} else {
nextOutput++;
if (nextOutput != i) {
lines->at(nextOutput) = lines->at(i);
}
// okay, we're done aggregating the current line, move to the next one
lines->at(++currentOutput) = lines->at(i);
}
}
if (lines->size() != nextOutput + 1) {
lines->resize(nextOutput + 1);
}
// possibly shrink the vector - currentOutput is the last line with valid data
lines->resize(currentOutput + 1);
}
// True if lhs equals to rhs, only compare iface, uid, tag and set.

View file

@ -291,7 +291,7 @@ TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) {
populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
std::vector<stats_line> lines;
ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
ASSERT_EQ((unsigned long)4, lines.size());
ASSERT_EQ((unsigned long)7, lines.size());
}
TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) {
@ -409,12 +409,18 @@ TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
.txPackets = TEST_PACKET0,
.txBytes = TEST_BYTES0,
};
StatsValue value3 = {
StatsValue value3 = { // value1 *2
.rxPackets = TEST_PACKET0 * 2,
.rxBytes = TEST_BYTES0 * 2,
.txPackets = TEST_PACKET1 * 2,
.txBytes = TEST_BYTES1 * 2,
};
StatsValue value5 = { // value2 + value3
.rxPackets = TEST_PACKET1 + TEST_PACKET0 * 2,
.rxBytes = TEST_BYTES1 + TEST_BYTES0 * 2,
.txPackets = TEST_PACKET0 + TEST_PACKET1 * 2,
.txBytes = TEST_BYTES0 + TEST_BYTES1 * 2,
};
std::vector<stats_line> lines;
@ -426,8 +432,9 @@ TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
// Test 1 line stats.
populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
ASSERT_EQ((size_t) 1, lines.size());
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
ASSERT_EQ((size_t) 2, lines.size()); // TEST_TAG != 0 -> 1 entry becomes 2 lines
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[0]);
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[1]);
lines.clear();
// These items should not be grouped.
@ -437,7 +444,7 @@ TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
mFakeStatsMap);
populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
ASSERT_EQ((size_t) 5, lines.size());
ASSERT_EQ((size_t) 9, lines.size());
lines.clear();
// These items should be grouped.
@ -445,14 +452,18 @@ TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
ASSERT_EQ((size_t) 5, lines.size());
ASSERT_EQ((size_t) 9, 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]);
expectStatsLineEqual(value5, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[0]);
expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, 0, lines[1]);
expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[2]);
expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[3]);
expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[4]);
expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines[5]);
expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[6]);
expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, 0, lines[7]);
expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[8]);
lines.clear();
// Perform test on IfaceStats.
@ -485,39 +496,48 @@ TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) {
.txPackets = TEST_PACKET1,
.txBytes = TEST_BYTES1,
};
StatsValue value4 = { // value1 * 4
.rxPackets = TEST_PACKET0 * 4,
.rxBytes = TEST_BYTES0 * 4,
.txPackets = TEST_PACKET1 * 4,
.txBytes = TEST_BYTES1 * 4,
};
// 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);
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, 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;
ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
ASSERT_EQ((size_t) 8, lines.size());
ASSERT_EQ((size_t) 12, lines.size());
// Uid 0 first
expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]);
expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, 0, lines[0]);
expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[1]);
// 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]);
expectStatsLineEqual(value4, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[2]);
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[3]);
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[4]);
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[5]);
// 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();
expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, 0, lines[6]);
expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]);
expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, 0, lines[8]);
expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[9]);
expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, 0, lines[10]);
expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[11]);
}
} // namespace bpf
} // namespace android