Let netd decide when to swap stats map
To protect stats map from overflow, netd need to know how many stats
entries exist in the current live stats map when tagging the socket. To
prevent racing against framework stats reading actions during tagging
sockets, let netd handle the map swap action instead.
Bug: 111441138
Test: android.app.usage.cts.NetworkUsageStatsTest
android.net.cts.TrafficStatsTest
Change-Id: I1b63e50a67be07472dba32744c8598c1788d0b75
diff --git a/server/TrafficController.cpp b/server/TrafficController.cpp
index 31d9ccd..04eee31 100644
--- a/server/TrafficController.cpp
+++ b/server/TrafficController.cpp
@@ -733,6 +733,48 @@
return mBpfLevel;
}
+Status TrafficController::swapActiveStatsMap() {
+ std::lock_guard guard(mOwnerMatchMutex);
+
+ if (mBpfLevel == BpfLevel::NONE) {
+ return statusFromErrno(EOPNOTSUPP, "This device doesn't have eBPF support");
+ }
+
+ uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+ auto oldConfiguration = mConfigurationMap.readValue(key);
+ if (!isOk(oldConfiguration)) {
+ ALOGE("Cannot read the old configuration from map: %s",
+ oldConfiguration.status().msg().c_str());
+ return oldConfiguration.status();
+ }
+
+ // Write to the configuration map to inform the kernel eBPF program to switch
+ // from using one map to the other. Use flag BPF_EXIST here since the map should
+ // be already populated in initMaps.
+ uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
+ Status res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure,
+ BPF_EXIST);
+ if (!isOk(res)) {
+ ALOGE("Failed to toggle the stats map: %s", strerror(res.code()));
+ return res;
+ }
+ // After changing the config, we need to make sure all the current running
+ // eBPF programs are finished and all the CPUs are aware of this config change
+ // before we modify the old map. So we do a special hack here to wait for
+ // the kernel to do a synchronize_rcu(). Once the kernel called
+ // synchronize_rcu(), the config we just updated will be available to all cores
+ // and the next eBPF programs triggered inside the kernel will use the new
+ // map configuration. So once this function returns we can safely modify the
+ // old stats map without concerning about race between the kernel and
+ // userspace.
+ int ret = synchronizeKernelRCU();
+ if (ret) {
+ ALOGE("map swap synchronize_rcu() ended with failure: %s", strerror(-ret));
+ return statusFromErrno(-ret, "map swap synchronize_rcu() failed");
+ }
+ return netdutils::status::ok;
+}
+
void TrafficController::setPermissionForUids(int permission, const std::vector<uid_t>& uids) {
if (permission == INetd::PERMISSION_UNINSTALLED) {
for (uid_t uid : uids) {