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/NetdNativeService.cpp b/server/NetdNativeService.cpp
index 5f1e2d0..b1b5e0b 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -807,6 +807,11 @@
     return asBinderStatus(gCtls->wakeupCtrl.delInterface(ifName, prefix, mark, mask));
 }
 
+binder::Status NetdNativeService::trafficSwapActiveStatsMap() {
+    ENFORCE_NETWORK_STACK_PERMISSIONS();
+    return asBinderStatus(gCtls->trafficCtrl.swapActiveStatsMap());
+}
+
 binder::Status NetdNativeService::idletimerAddInterface(const std::string& ifName, int32_t timeout,
                                                         const std::string& classLabel) {
     NETD_LOCKING_RPC(gCtls->idletimerCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index def4a3a..518530c 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -196,6 +196,8 @@
                                              int32_t direction, int32_t markValue, int32_t markMask,
                                              int32_t interfaceId);
 
+    binder::Status trafficSwapActiveStatsMap() override;
+
     binder::Status ipSecAddTunnelInterface(const std::string& deviceName,
                                            const std::string& localAddress,
                                            const std::string& remoteAddress, int32_t iKey,
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) {
diff --git a/server/TrafficController.h b/server/TrafficController.h
index cd4f88d..d30749b 100644
--- a/server/TrafficController.h
+++ b/server/TrafficController.h
@@ -88,6 +88,11 @@
     bpf::BpfLevel getBpfLevel();
 
     /*
+     * Swap the stats map config from current active stats map to the idle one.
+     */
+    netdutils::Status swapActiveStatsMap();
+
+    /*
      * Add the interface name and index pair into the eBPF map.
      */
     int addInterface(const char* name, uint32_t ifaceIndex);
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index fd0a079..a7f0954 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -1180,4 +1180,11 @@
      *         cause of the failure.
      */
     void firewallRemoveUidInterfaceRules(in int[] uids);
+
+   /**
+    * Request netd to change the current active network stats map.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void trafficSwapActiveStatsMap();
 }