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
This commit is contained in:
Sarup Dalwani 2025-01-23 20:02:48 +00:00 • committed by Jacque Li
commit 448eb43f28
4 changed files with 415 additions and 0 deletions

View file

@ -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
+ "}";
}
}

View file

@ -60,6 +60,11 @@ public class BpfNetMapsConstants {
"/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);

View file

@ -25,6 +25,8 @@ import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
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.BpfMap;
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.CookieTagMapKey;
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 @@ public class BpfNetMaps {
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 @@ public class BpfNetMaps {
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 @@ public class BpfNetMaps {
}
}
@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 @@ public class BpfNetMaps {
} 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 @@ public class BpfNetMaps {
}
}
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) {
@ -809,6 +891,113 @@ public class BpfNetMaps {
}
}
/**
* 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.
@ -1079,6 +1268,14 @@ public class BpfNetMaps {
(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();
}

View file

@ -71,6 +71,7 @@ import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
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 androidx.test.filters.SmallTest;
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.CookieTagMapKey;
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 @@ public final class BpfNetMapsTest {
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 @@ public final class BpfNetMapsTest {
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));
@ -234,6 +243,138 @@ public final class BpfNetMapsTest {
doTestIsChainEnabled(List.of(enableChain));
}
@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 {