Switch SkDestroyListener based on the flag.
Switch existing C SkDestroyListener and new Java SkDestroyListener based
on the experiment flag.
Bug: 217624062
Test: atest SkDestroyListenerTest
Change-Id: I7031ba7c155edb43e0e7b77449587058d6ba38fb
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index bf37eca..f80d7e8 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -162,11 +162,13 @@
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkStatsUtils;
import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.SharedLog;
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;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.server.BpfNetMaps;
import java.io.File;
import java.io.FileDescriptor;
@@ -454,6 +456,9 @@
@NonNull
private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+ @Nullable
+ private final SkDestroyListener mSkDestroyListener;
+
private static @NonNull Clock getDefaultClock() {
return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
Clock.systemUTC());
@@ -587,6 +592,18 @@
mStatsMapA = mDeps.getStatsMapA();
mStatsMapB = mDeps.getStatsMapB();
mAppUidStatsMap = mDeps.getAppUidStatsMap();
+
+ // TODO: Remove bpfNetMaps creation and always start SkDestroyListener
+ // Following code is for the experiment to verify the SkDestroyListener refactoring. Based
+ // on the experiment flag, BpfNetMaps starts C SkDestroyListener (existing code) or
+ // NetworkStatsService starts Java SkDestroyListener (new code).
+ final BpfNetMaps bpfNetMaps = mDeps.makeBpfNetMaps(mContext);
+ if (bpfNetMaps.isSkDestroyListenerRunning()) {
+ mSkDestroyListener = null;
+ } else {
+ mSkDestroyListener = mDeps.makeSkDestroyListener(mCookieTagMap, mHandler);
+ mHandler.post(mSkDestroyListener::start);
+ }
}
/**
@@ -782,6 +799,17 @@
public boolean isDebuggable() {
return Build.isDebuggable();
}
+
+ /** Create a new BpfNetMaps. */
+ public BpfNetMaps makeBpfNetMaps(Context ctx) {
+ return new BpfNetMaps(ctx);
+ }
+
+ /** Create a new SkDestroyListener. */
+ public SkDestroyListener makeSkDestroyListener(
+ IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
+ return new SkDestroyListener(cookieTagMap, handler, new SharedLog(TAG));
+ }
}
/**
diff --git a/service-t/src/com/android/server/net/SkDestroyListener.java b/service-t/src/com/android/server/net/SkDestroyListener.java
new file mode 100644
index 0000000..7b68f89
--- /dev/null
+++ b/service-t/src/com/android/server/net/SkDestroyListener.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+
+import android.os.Handler;
+import android.system.ErrnoException;
+
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.bpf.CookieTagMapKey;
+import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.net.module.util.ip.NetlinkMonitor;
+import com.android.net.module.util.netlink.InetDiagMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.StructInetDiagSockId;
+
+/**
+ * Monitor socket destroy and delete entry from cookie tag bpf map.
+ */
+public class SkDestroyListener extends NetlinkMonitor {
+ private static final int SKNLGRP_INET_TCP_DESTROY = 1;
+ private static final int SKNLGRP_INET_UDP_DESTROY = 2;
+ private static final int SKNLGRP_INET6_TCP_DESTROY = 3;
+ private static final int SKNLGRP_INET6_UDP_DESTROY = 4;
+
+ // TODO: if too many sockets are closed too quickly, this can overflow the socket buffer, and
+ // some entries in mCookieTagMap will not be freed. In order to fix this it would be needed to
+ // periodically dump all sockets and remove the tag entries for sockets that have been closed.
+ // For now, set a large-enough buffer that hundreds of sockets can be closed without getting
+ // ENOBUFS and leaking mCookieTagMap entries.
+ private static final int SOCK_RCV_BUF_SIZE = 512 * 1024;
+
+ private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
+
+ SkDestroyListener(final IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap,
+ final Handler handler, final SharedLog log) {
+ super(handler, log, "SkDestroyListener", NETLINK_INET_DIAG,
+ 1 << (SKNLGRP_INET_TCP_DESTROY - 1)
+ | 1 << (SKNLGRP_INET_UDP_DESTROY - 1)
+ | 1 << (SKNLGRP_INET6_TCP_DESTROY - 1)
+ | 1 << (SKNLGRP_INET6_UDP_DESTROY - 1),
+ SOCK_RCV_BUF_SIZE);
+ mCookieTagMap = cookieTagMap;
+ }
+
+ @Override
+ public void processNetlinkMessage(final NetlinkMessage nlMsg, final long whenMs) {
+ if (!(nlMsg instanceof InetDiagMessage)) {
+ mLog.e("Received non InetDiagMessage");
+ return;
+ }
+ final StructInetDiagSockId sockId = ((InetDiagMessage) nlMsg).inetDiagMsg.id;
+ try {
+ mCookieTagMap.deleteEntry(new CookieTagMapKey(sockId.cookie));
+ } catch (ErrnoException e) {
+ mLog.e("Failed to delete CookieTagMap entry for " + sockId.cookie + ": " + e);
+ }
+ }
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 97449f6..19c5a27 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -225,10 +225,14 @@
Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap);
initBpfMaps();
- native_init(true /* startSkDestroyListener */);
+ native_init(!sEnableJavaBpfMap /* startSkDestroyListener */);
sInitialized = true;
}
+ public boolean isSkDestroyListenerRunning() {
+ return !sEnableJavaBpfMap;
+ }
+
/**
* Dependencies of BpfNetMaps, for injection in tests.
*/
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index ac1bb4f..d560dc2 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -143,6 +143,7 @@
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.server.BpfNetMaps;
import com.android.server.net.NetworkStatsService.AlertObserver;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -248,6 +249,10 @@
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
private TestBpfMap<U32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(U32.class, U8.class));
+ @Mock
+ private BpfNetMaps mBpfNetMaps;
+ @Mock
+ private SkDestroyListener mSkDestroyListener;
private TestBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap = new TestBpfMap<>(
CookieTagMapKey.class, CookieTagMapValue.class);
@@ -500,6 +505,17 @@
public boolean isDebuggable() {
return mIsDebuggable == Boolean.TRUE;
}
+
+ @Override
+ public BpfNetMaps makeBpfNetMaps(Context ctx) {
+ return mBpfNetMaps;
+ }
+
+ @Override
+ public SkDestroyListener makeSkDestroyListener(
+ IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
+ return mSkDestroyListener;
+ }
};
}