diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java index bf37eca085..f80d7e8b3e 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.IBpfMap; 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 class NetworkStatsService extends INetworkStatsService.Stub { 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 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 0000000000..7b68f8989a --- /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 mCookieTagMap; + + SkDestroyListener(final IBpfMap 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 97449f6cb8..19c5a27b27 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -225,10 +225,14 @@ public class BpfNetMaps { 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 ac1bb4fb46..d560dc23fd 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.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 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 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { @Mock private LocationPermissionChecker mLocationPermissionChecker; private TestBpfMap mUidCounterSetMap = spy(new TestBpfMap<>(U32.class, U8.class)); + @Mock + private BpfNetMaps mBpfNetMaps; + @Mock + private SkDestroyListener mSkDestroyListener; private TestBpfMap mCookieTagMap = new TestBpfMap<>( CookieTagMapKey.class, CookieTagMapValue.class); @@ -500,6 +505,17 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public boolean isDebuggable() { return mIsDebuggable == Boolean.TRUE; } + + @Override + public BpfNetMaps makeBpfNetMaps(Context ctx) { + return mBpfNetMaps; + } + + @Override + public SkDestroyListener makeSkDestroyListener( + IBpfMap cookieTagMap, Handler handler) { + return mSkDestroyListener; + } }; }