From 166f966209973f3ea9a38d53bb6bef33cf02371b Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Thu, 1 Sep 2022 10:35:29 +0900 Subject: [PATCH] register callback to pull NetworkBpfMapInfo atom Test: statsd_testdrive 10161, atest BpfNetMaps Bug: 217624062 Bug: 245228691 Change-Id: I630a86cb5a34aaa1810eb75b374588dba570f0ff --- service/Android.bp | 9 ++++ .../src/com/android/server/BpfNetMaps.java | 53 +++++++++++++++++++ .../android/server/ConnectivityService.java | 5 ++ .../com/android/server/BpfNetMapsTest.java | 46 +++++++++++++++- 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/service/Android.bp b/service/Android.bp index b68d3895e2..68c1722bf0 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -143,6 +143,7 @@ java_library { "src/**/*.java", ":framework-connectivity-shared-srcs", ":services-connectivity-shared-srcs", + ":statslog-connectivity-java-gen", ], libs: [ "framework-annotations-lib", @@ -152,6 +153,7 @@ java_library { "framework-wifi.stubs.module_lib", "unsupportedappusage", "ServiceConnectivityResources", + "framework-statsd.stubs.module_lib", ], static_libs: [ // Do not add libs here if they are already included @@ -332,3 +334,10 @@ java_genrule { "--output $(out)", visibility: ["//visibility:private"], } + +genrule { + name: "statslog-connectivity-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module connectivity --javaPackage com.android.server --javaClass ConnectivityStatsLog", + out: ["com/android/server/ConnectivityStatsLog.java"], +} diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index 4eeef5c558..69b6486a79 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -33,6 +33,9 @@ import static android.system.OsConstants.ENODEV; import static android.system.OsConstants.ENOENT; import static android.system.OsConstants.EOPNOTSUPP; +import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO; + +import android.app.StatsManager; import android.content.Context; import android.net.INetd; import android.os.RemoteException; @@ -42,12 +45,15 @@ import android.system.ErrnoException; import android.system.Os; import android.util.ArraySet; import android.util.Log; +import android.util.StatsEvent; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.BackgroundThread; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BpfMap; import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.IBpfMap; +import com.android.net.module.util.Struct; import com.android.net.module.util.Struct.U32; import com.android.net.module.util.Struct.U8; import com.android.net.module.util.bpf.CookieTagMapKey; @@ -55,6 +61,7 @@ import com.android.net.module.util.bpf.CookieTagMapValue; import java.io.FileDescriptor; import java.io.IOException; +import java.util.List; import java.util.Set; /** @@ -274,6 +281,15 @@ public class BpfNetMaps { public int synchronizeKernelRCU() { return native_synchronizeKernelRCU(); } + + /** + * Build Stats Event for NETWORK_BPF_MAP_INFO atom + */ + public StatsEvent buildStatsEvent(final int cookieTagMapSize, final int uidOwnerMapSize, + final int uidPermissionMapSize) { + return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize, + uidOwnerMapSize, uidPermissionMapSize); + } } /** Constructor used after T that doesn't need to use netd anymore. */ @@ -849,6 +865,43 @@ public class BpfNetMaps { } } + /** Register callback for statsd to pull atom. */ + public void setPullAtomCallback(final Context context) { + throwIfPreT("setPullAtomCallback is not available on pre-T devices"); + + final StatsManager statsManager = context.getSystemService(StatsManager.class); + statsManager.setPullAtomCallback(NETWORK_BPF_MAP_INFO, null /* metadata */, + BackgroundThread.getExecutor(), this::pullBpfMapInfoAtom); + } + + private int getMapSize(IBpfMap map) + throws ErrnoException { + // forEach could restart iteration from the beginning if there is a concurrent entry + // deletion. netd and skDestroyListener could delete CookieTagMap entry concurrently. + // So using Set to count the number of entry in the map. + Set keySet = new ArraySet<>(); + map.forEach((k, v) -> keySet.add(k)); + return keySet.size(); + } + + /** Callback for StatsManager#setPullAtomCallback */ + @VisibleForTesting + public int pullBpfMapInfoAtom(final int atomTag, final List data) { + if (atomTag != NETWORK_BPF_MAP_INFO) { + Log.e(TAG, "Unexpected atom tag: " + atomTag); + return StatsManager.PULL_SKIP; + } + + try { + data.add(mDeps.buildStatsEvent(getMapSize(sCookieTagMap), getMapSize(sUidOwnerMap), + getMapSize(sUidPermissionMap))); + } catch (ErrnoException e) { + Log.e(TAG, "Failed to pull NETWORK_BPF_MAP_INFO atom: " + e); + return StatsManager.PULL_SKIP; + } + return StatsManager.PULL_SUCCESS; + } + /** * Dump BPF maps * diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index afb3ca23e9..36c81dd225 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -3040,6 +3040,11 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) { updateMobileDataPreferredUids(); } + + // On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom + if (SdkLevel.isAtLeastT()) { + mBpfNetMaps.setPullAtomCallback(mContext); + } } /** diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java index b7d80acbbb..eb5d2ef2f7 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -30,6 +30,7 @@ import static android.net.INetd.PERMISSION_INTERNET; import static android.net.INetd.PERMISSION_NONE; import static android.net.INetd.PERMISSION_UNINSTALLED; import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS; +import static android.system.OsConstants.EINVAL; import static android.system.OsConstants.EPERM; import static com.android.server.BpfNetMaps.DOZABLE_MATCH; @@ -40,6 +41,7 @@ import static com.android.server.BpfNetMaps.NO_MATCH; import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH; import static com.android.server.BpfNetMaps.POWERSAVE_MATCH; import static com.android.server.BpfNetMaps.RESTRICTED_MATCH; +import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -47,13 +49,18 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.app.StatsManager; import android.content.Context; import android.net.INetd; import android.os.Build; import android.os.ServiceSpecificException; +import android.system.ErrnoException; import androidx.test.filters.SmallTest; @@ -76,6 +83,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; @RunWith(DevSdkIgnoreRunner.class) @@ -120,7 +128,7 @@ public final class BpfNetMapsTest { new TestBpfMap<>(U32.class, UidOwnerValue.class); private final IBpfMap mUidPermissionMap = new TestBpfMap<>(U32.class, U8.class); private final IBpfMap mCookieTagMap = - new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class); + spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class)); @Before public void setUp() throws Exception { @@ -882,4 +890,40 @@ public final class BpfNetMapsTest { assertThrows(ServiceSpecificException.class, () -> mBpfNetMaps.swapActiveStatsMap()); } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testPullBpfMapInfo() throws Exception { + // mCookieTagMap has 1 entry + mCookieTagMap.updateEntry(new CookieTagMapKey(0), new CookieTagMapValue(0, 0)); + + // mUidOwnerMap has 2 entries + mUidOwnerMap.updateEntry(new U32(0), new UidOwnerValue(0, 0)); + mUidOwnerMap.updateEntry(new U32(1), new UidOwnerValue(0, 0)); + + // mUidPermissionMap has 3 entries + mUidPermissionMap.updateEntry(new U32(0), new U8((short) 0)); + mUidPermissionMap.updateEntry(new U32(1), new U8((short) 0)); + mUidPermissionMap.updateEntry(new U32(2), new U8((short) 0)); + + final int ret = mBpfNetMaps.pullBpfMapInfoAtom(NETWORK_BPF_MAP_INFO, new ArrayList<>()); + assertEquals(StatsManager.PULL_SUCCESS, ret); + verify(mDeps).buildStatsEvent( + 1 /* cookieTagMapSize */, 2 /* uidOwnerMapSize */, 3 /* uidPermissionMapSize */); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testPullBpfMapInfoGetMapSizeFailure() throws Exception { + doThrow(new ErrnoException("", EINVAL)).when(mCookieTagMap).forEach(any()); + final int ret = mBpfNetMaps.pullBpfMapInfoAtom(NETWORK_BPF_MAP_INFO, new ArrayList<>()); + assertEquals(StatsManager.PULL_SKIP, ret); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testPullBpfMapInfoUnexpectedAtomTag() { + final int ret = mBpfNetMaps.pullBpfMapInfoAtom(-1 /* atomTag */, new ArrayList<>()); + assertEquals(StatsManager.PULL_SKIP, ret); + } }