Adding access methods for local_net_access_map and
local_net_blocked_uid_map

Test: Manually tested this using dumpsys commands
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.BpfNetMapsTest
Bug: 392855578

Change-Id: I61522f3282aa502f20641d3c2692a66638edf706
diff --git a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
new file mode 100644
index 0000000..95265b9
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.net.module.util.bpf;
+
+import com.android.net.module.util.InetAddressUtils;
+import com.android.net.module.util.Struct;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+public class LocalNetAccessKey extends Struct {
+
+    @Field(order = 0, type = Type.U32)
+    public final long lpmBitlen;
+    @Field(order = 1, type = Type.U32)
+    public final long ifIndex;
+    @Field(order = 2, type = Type.Ipv6Address)
+    public final Inet6Address remoteAddress;
+    @Field(order = 3, type = Type.U16)
+    public final int protocol;
+    @Field(order = 4, type = Type.UBE16)
+    public final int remotePort;
+
+    public LocalNetAccessKey(long lpmBitlen, long ifIndex, InetAddress remoteAddress, int protocol,
+            int remotePort) {
+        this.lpmBitlen = lpmBitlen;
+        this.ifIndex = ifIndex;
+        this.protocol = protocol;
+        this.remotePort = remotePort;
+
+        if (remoteAddress instanceof Inet4Address) {
+            this.remoteAddress = InetAddressUtils.v4MappedV6Address((Inet4Address) remoteAddress);
+        } else {
+            this.remoteAddress = (Inet6Address) remoteAddress;
+        }
+    }
+
+    public LocalNetAccessKey(long lpmBitlen, long ifIndex, Inet6Address remoteAddress, int protocol,
+            int remotePort) {
+        this.lpmBitlen = lpmBitlen;
+        this.ifIndex = ifIndex;
+        this.remoteAddress = remoteAddress;
+        this.protocol = protocol;
+        this.remotePort = remotePort;
+    }
+
+    @Override
+    public String toString() {
+        return "LocalNetAccessKey{"
+                + "lpmBitlen=" + lpmBitlen
+                + ", ifIndex=" + ifIndex
+                + ", remoteAddress=" + remoteAddress
+                + ", protocol=" + protocol
+                + ", remotePort=" + remotePort
+                + "}";
+    }
+}
diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
index f3773de..f1a6f00 100644
--- a/framework/src/android/net/BpfNetMapsConstants.java
+++ b/framework/src/android/net/BpfNetMapsConstants.java
@@ -60,6 +60,11 @@
             "/sys/fs/bpf/netd_shared/map_netd_data_saver_enabled_map";
     public static final String INGRESS_DISCARD_MAP_PATH =
             "/sys/fs/bpf/netd_shared/map_netd_ingress_discard_map";
+    public static final String LOCAL_NET_ACCESS_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_local_net_access_map";
+    public static final String LOCAL_NET_BLOCKED_UID_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_local_net_blocked_uid_map";
+
     public static final Struct.S32 UID_RULES_CONFIGURATION_KEY = new Struct.S32(0);
     public static final Struct.S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new Struct.S32(1);
     public static final Struct.S32 DATA_SAVER_ENABLED_KEY = new Struct.S32(0);
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 44868b2d..c743573 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -25,6 +25,8 @@
 import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
 import static android.net.BpfNetMapsConstants.IIF_MATCH;
 import static android.net.BpfNetMapsConstants.INGRESS_DISCARD_MAP_PATH;
+import static android.net.BpfNetMapsConstants.LOCAL_NET_ACCESS_MAP_PATH;
+import static android.net.BpfNetMapsConstants.LOCAL_NET_BLOCKED_UID_MAP_PATH;
 import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
 import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
 import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
@@ -74,6 +76,7 @@
 import com.android.net.module.util.IBpfMap;
 import com.android.net.module.util.SingleWriterBpfMap;
 import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Bool;
 import com.android.net.module.util.Struct.S32;
 import com.android.net.module.util.Struct.U32;
 import com.android.net.module.util.Struct.U8;
@@ -81,6 +84,7 @@
 import com.android.net.module.util.bpf.CookieTagMapValue;
 import com.android.net.module.util.bpf.IngressDiscardKey;
 import com.android.net.module.util.bpf.IngressDiscardValue;
+import com.android.net.module.util.bpf.LocalNetAccessKey;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -131,6 +135,9 @@
     private static IBpfMap<S32, U8> sDataSaverEnabledMap = null;
     private static IBpfMap<IngressDiscardKey, IngressDiscardValue> sIngressDiscardMap = null;
 
+    private static IBpfMap<LocalNetAccessKey, Bool> sLocalNetAccessMap = null;
+    private static IBpfMap<U32, Bool> sLocalNetBlockedUidMap = null;
+
     private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
             Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
             Pair.create(PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
@@ -186,6 +193,25 @@
         sIngressDiscardMap = ingressDiscardMap;
     }
 
+    /**
+     * Set localNetAccessMap for test.
+     */
+    @VisibleForTesting
+    public static void setLocalNetAccessMapForTest(
+            IBpfMap<LocalNetAccessKey, Bool> localNetAccessMap) {
+        sLocalNetAccessMap = localNetAccessMap;
+    }
+
+    /**
+     * Set localNetBlockedUidMap for test.
+     */
+    @VisibleForTesting
+    public static void setLocalNetBlockedUidMapForTest(
+            IBpfMap<U32, Bool> localNetBlockedUidMap) {
+        sLocalNetBlockedUidMap = localNetBlockedUidMap;
+    }
+
+
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static IBpfMap<S32, U32> getConfigurationMap() {
         try {
@@ -247,6 +273,26 @@
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    private static IBpfMap<U32, Bool> getLocalNetBlockedUidMap() {
+        try {
+            return SingleWriterBpfMap.getSingleton(LOCAL_NET_BLOCKED_UID_MAP_PATH,
+                    U32.class, Bool.class);
+        } catch (ErrnoException e) {
+            throw new IllegalStateException("Cannot open local_net_blocked_uid map", e);
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    private static IBpfMap<LocalNetAccessKey, Bool> getLocalNetAccessMap() {
+        try {
+            return SingleWriterBpfMap.getSingleton(LOCAL_NET_ACCESS_MAP_PATH,
+                    LocalNetAccessKey.class, Bool.class);
+        } catch (ErrnoException e) {
+            throw new IllegalStateException("Cannot open local_net_access map", e);
+        }
+    }
+
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static void initBpfMaps() {
         if (sConfigurationMap == null) {
@@ -299,6 +345,27 @@
         } catch (ErrnoException e) {
             throw new IllegalStateException("Failed to initialize ingress discard map", e);
         }
+
+        if (isAtLeast25Q2()) {
+            if (sLocalNetAccessMap == null) {
+                sLocalNetAccessMap = getLocalNetAccessMap();
+            }
+            try {
+                sLocalNetAccessMap.clear();
+            } catch (ErrnoException e) {
+                throw new IllegalStateException("Failed to initialize local_net_access map", e);
+            }
+
+            if (sLocalNetBlockedUidMap == null) {
+                sLocalNetBlockedUidMap = getLocalNetBlockedUidMap();
+            }
+            try {
+                sLocalNetBlockedUidMap.clear();
+            } catch (ErrnoException e) {
+                throw new IllegalStateException("Failed to initialize local_net_blocked_uid map",
+                        e);
+            }
+        }
     }
 
     /**
@@ -387,6 +454,21 @@
         }
     }
 
+    private void throwIfPre25Q2(final String msg) {
+        if (!isAtLeast25Q2()) {
+            throw new UnsupportedOperationException(msg);
+        }
+    }
+
+    /*
+     ToDo : Remove this method when SdkLevel.isAtLeastB() is fixed, aosp is at sdk level 36 or use
+     NetworkStackUtils.isAtLeast25Q2 when it is moved to a static lib.
+     */
+    private static boolean isAtLeast25Q2() {
+        return SdkLevel.isAtLeastB()  || (SdkLevel.isAtLeastV()
+                && "Baklava".equals(Build.VERSION.CODENAME));
+    }
+
     private void removeRule(final int uid, final long match, final String caller) {
         try {
             synchronized (sUidOwnerMap) {
@@ -810,6 +892,113 @@
     }
 
     /**
+     * Add configuration to local_net_access trie map.
+     * @param lpmBitlen prefix length that will be used for longest matching
+     * @param iface interface name
+     * @param address remote address. ipv4 addresses would be mapped to v6
+     * @param protocol required for longest match in special cases
+     * @param remotePort src/dst port for ingress/egress
+     * @param isAllowed is the local network call allowed or blocked.
+     */
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    public void addLocalNetAccess(final int lpmBitlen, final String iface,
+            final InetAddress address, final int protocol, final int remotePort,
+            final boolean isAllowed) {
+        throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
+        final int ifIndex = mDeps.getIfIndex(iface);
+        if (ifIndex == 0) {
+            Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address
+                    + "(" + iface + ")");
+            return;
+        }
+        LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+                address, protocol, remotePort);
+
+        try {
+            sLocalNetAccessMap.updateEntry(localNetAccessKey, new Bool(isAllowed));
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to add local network access for localNetAccessKey : "
+                    + localNetAccessKey + ", isAllowed : " + isAllowed);
+        }
+    }
+
+    /**
+     * False if the configuration is disallowed.
+     *
+     * @param lpmBitlen  prefix length that will be used for longest matching
+     * @param iface    interface name
+     * @param address    remote address. ipv4 addresses would be mapped to v6
+     * @param protocol   required for longest match in special cases
+     * @param remotePort src/dst port for ingress/egress
+     */
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
+            final InetAddress address, final int protocol, final int remotePort) {
+        throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices");
+        final int ifIndex = mDeps.getIfIndex(iface);
+        if (ifIndex == 0) {
+            Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for "
+                    + address + "(" + iface + ")");
+            return true;
+        }
+        LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+                address, protocol, remotePort);
+        try {
+            Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
+            return value == null ? true : value.val;
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to find local network access configuration for "
+                    + "localNetAccessKey : " + localNetAccessKey);
+        }
+        return true;
+    }
+
+    /**
+     * Add uid to local_net_blocked_uid map.
+     * @param uid application uid that needs to block local network calls.
+     */
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    public void addUidToLocalNetBlockMap(final int uid) {
+        throwIfPre25Q2("addUidToLocalNetBlockMap is not available on pre-B devices");
+        try {
+            sLocalNetBlockedUidMap.updateEntry(new U32(uid), new Bool(true));
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to add local network blocked for uid : " + uid);
+        }
+    }
+
+    /**
+     * True if local network calls are blocked for application.
+     * @param uid application uid that needs check if local network calls are blocked.
+     */
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    public boolean getUidValueFromLocalNetBlockMap(final int uid) {
+        throwIfPre25Q2("getUidValueFromLocalNetBlockMap is not available on pre-B devices");
+        try {
+            Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
+            return value == null ? false : value.val;
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to find uid(" + uid
+                    + ") is present in local network blocked map");
+        }
+        return false;
+    }
+
+    /**
+     * Remove uid from local_net_blocked_uid map(if present).
+     * @param uid application uid that needs check if local network calls are blocked.
+     */
+    @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+    public void removeUidFromLocalNetBlockMap(final int uid) {
+        throwIfPre25Q2("removeUidFromLocalNetBlockMap is not available on pre-B devices");
+        try {
+            sLocalNetBlockedUidMap.deleteEntry(new U32(uid));
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to remove uid(" + uid + ") from local network blocked map");
+        }
+    }
+
+    /**
      * Get granted permissions for specified uid. If uid is not in the map, this method returns
      * {@link android.net.INetd.PERMISSION_INTERNET} since this is a default permission.
      * See {@link #setNetPermForUids}
@@ -1079,6 +1268,14 @@
                     (key, value) -> "[" + key.dstAddr + "]: "
                             + value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
                             + value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
+            if (sLocalNetBlockedUidMap != null) {
+                BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap",
+                        (key, value) -> "[" + key + "]: " + value);
+            }
+            if (sLocalNetBlockedUidMap != null) {
+                BpfDump.dumpMap(sLocalNetBlockedUidMap, pw, "sLocalNetBlockedUidMap",
+                        (key, value) -> "[" + key + "]: " + value);
+            }
             dumpDataSaverConfig(pw);
             pw.decreaseIndent();
         }
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index c1c15ca..73eb24d 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -71,6 +71,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -99,6 +100,7 @@
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.Bool;
 import com.android.net.module.util.Struct.S32;
 import com.android.net.module.util.Struct.U32;
 import com.android.net.module.util.Struct.U8;
@@ -106,6 +108,7 @@
 import com.android.net.module.util.bpf.CookieTagMapValue;
 import com.android.net.module.util.bpf.IngressDiscardKey;
 import com.android.net.module.util.bpf.IngressDiscardValue;
+import com.android.net.module.util.bpf.LocalNetAccessKey;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -171,6 +174,10 @@
     private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap =
             new TestBpfMap<>(S32.class, UidOwnerValue.class);
     private final IBpfMap<S32, U8> mUidPermissionMap = new TestBpfMap<>(S32.class, U8.class);
+    private final IBpfMap<U32, Bool> mLocalNetBlockedUidMap =
+            new TestBpfMap<>(U32.class, Bool.class);
+    private final IBpfMap<LocalNetAccessKey, Bool> mLocalNetAccessMap =
+            new TestBpfMap<>(LocalNetAccessKey.class, Bool.class);
     private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap =
             spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class));
     private final IBpfMap<S32, U8> mDataSaverEnabledMap = new TestBpfMap<>(S32.class, U8.class);
@@ -189,6 +196,8 @@
                 CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
         BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
         BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
+        BpfNetMaps.setLocalNetAccessMapForTest(mLocalNetAccessMap);
+        BpfNetMaps.setLocalNetBlockedUidMapForTest(mLocalNetBlockedUidMap);
         BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
         BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap);
         mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
@@ -235,6 +244,138 @@
     }
 
     @Test
+    @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testAddLocalNetAccessBeforeV() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.addLocalNetAccess(0, TEST_IF_NAME, Inet6Address.ANY, 0, 0, true));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testAddLocalNetAccessAfterV() throws Exception {
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+                Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+        assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("196.68.0.0"), 0, 0)));
+        assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("100.68.0.0"), 0, 0)));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testAddLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mBpfNetMaps.addLocalNetAccess(160, "wlan2",
+                Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+        assertTrue(mLocalNetAccessMap.isEmpty());
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testGetLocalNetAccessBeforeV() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.getLocalNetAccess(0, TEST_IF_NAME, Inet6Address.ANY, 0, 0));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testGetLocalNetAccessAfterV() throws Exception {
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mLocalNetAccessMap.updateEntry(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("196.68.0.0"), 0, 0),
+                new Bool(false));
+
+        assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+                Inet4Address.getByName("196.68.0.0"), 0, 0)));
+
+        assertFalse(mBpfNetMaps.getLocalNetAccess(160, TEST_IF_NAME,
+                Inet4Address.getByName("196.68.0.0"), 0, 0));
+        assertTrue(mBpfNetMaps.getLocalNetAccess(160, TEST_IF_NAME,
+                Inet4Address.getByName("100.68.0.0"), 0, 0));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testAddUidToLocalNetBlockMapBeforeV() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.addUidToLocalNetBlockMap(0));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testIsUidPresentInLocalNetBlockMapBeforeV() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.getUidValueFromLocalNetBlockMap(0));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testRemoveUidFromLocalNetBlockMapBeforeV() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.removeUidFromLocalNetBlockMap(0));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testAddUidFromLocalNetBlockMapAfterV() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mBpfNetMaps.addUidToLocalNetBlockMap(uid0);
+        assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid0)).val);
+        assertNull(mLocalNetBlockedUidMap.getValue(new U32(uid1)));
+
+        mBpfNetMaps.addUidToLocalNetBlockMap(uid1);
+        assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid1)).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testIsUidPresentInLocalNetBlockMapAfterV() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mLocalNetBlockedUidMap.updateEntry(new U32(uid0), new Bool(true));
+        assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid0));
+        assertFalse(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+
+        mLocalNetBlockedUidMap.updateEntry(new U32(uid1), new Bool(true));
+        assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public void testRemoveUidFromLocalNetBlockMapAfterV() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+
+        assertTrue(mLocalNetAccessMap.isEmpty());
+
+        mLocalNetBlockedUidMap.updateEntry(new U32(uid0), new Bool(true));
+        mLocalNetBlockedUidMap.updateEntry(new U32(uid1), new Bool(true));
+
+        assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid0)).val);
+        assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid1)).val);
+
+        mBpfNetMaps.removeUidFromLocalNetBlockMap(uid0);
+        assertNull(mLocalNetBlockedUidMap.getValue(new U32(uid0)));
+        assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid1)).val);
+
+        mBpfNetMaps.removeUidFromLocalNetBlockMap(uid1);
+        assertNull(mLocalNetBlockedUidMap.getValue(new U32(uid1)));
+    }
+
+    @Test
     @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     public void testIsChainEnabled() throws Exception {
         doTestIsChainEnabled(FIREWALL_CHAIN_DOZABLE);