BpfMapTest: add fd leak test

Test that there is no fd leak with persistent fd cache.

Bug: 236320567
Test: atest BpfMapTest#testNoFdLeaks (cached fd) [PASSED]
Test: atest BpfMapTest#testNoFdLeaks (noncached fd) [FAILED] (as expected)
[8/14] com.android.networkstack.tethering.BpfMapTest#testNoFdLeaks: FAILED (241ms)

STACKTRACE:
java.lang.AssertionError: Fd leak after 1000 iterations:  expected:<89> but was:<1089>
	at org.junit.Assert.fail(Assert.java:89)
	at org.junit.Assert.failNotEquals(Assert.java:835)
	at org.junit.Assert.assertEquals(Assert.java:647)
	at com.android.networkstack.tethering.BpfMapTest.testNoFdLeaks(BpfMapTest.java:420)

Test Code:
fd noncached BpfMap
frameworks/libs/net/common/device/com/android/net/module/util/BpfMap.java
@@ -97,7 +97,7 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>
      */
     public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
             final Class<V> value) throws ErrnoException, NullPointerException {
-        mMapFd = cachedBpfFdGet(path, flag);
+        mMapFd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, flag));

Change-Id: I66f477fd1c291c56bccc97d385b2a554c2367b5a
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 68c1c57..536ab2d 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -30,6 +30,7 @@
 import android.net.MacAddress;
 import android.os.Build;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.ArrayMap;
 
@@ -42,6 +43,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.net.InetAddress;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -393,4 +395,34 @@
             assertEquals(OsConstants.ENOENT, expected.errno);
         }
     }
+
+    private static int getNumOpenFds() {
+        return new File("/proc/" + Os.getpid() + "/fd").listFiles().length;
+    }
+
+    @Test
+    public void testNoFdLeaks() throws Exception {
+        // Due to #setUp has called #initTestMap to open map and BpfMap is using persistent fd
+        // cache, expect that the fd amount is not increased in the iterations.
+        // See the comment of BpfMap#close.
+        final int iterations = 1000;
+        final int before = getNumOpenFds();
+        for (int i = 0; i < iterations; i++) {
+            try (BpfMap<TetherDownstream6Key, Tether6Value> map = new BpfMap<>(
+                TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+                TetherDownstream6Key.class, Tether6Value.class)) {
+                // do nothing
+            }
+        }
+        final int after = getNumOpenFds();
+
+        // Check that the number of open fds is the same as before.
+        // If this exact match becomes flaky, we probably need to distinguish that fd is belong
+        // to "bpf-map".
+        // ex:
+        // $ adb shell ls -all /proc/16196/fd
+        // [..] network_stack 64 2022-07-26 22:01:02.300002956 +0800 749 -> anon_inode:bpf-map
+        // [..] network_stack 64 2022-07-26 22:01:02.188002956 +0800 75 -> anon_inode:[eventfd]
+        assertEquals("Fd leak after " + iterations + " iterations: ", before, after);
+    }
 }