Merge "[BR01.1] Support BpfNetMapsReader" into main am: a62eed380c

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2725213

Change-Id: I0a9955e7fa9d51b423f8cd1113553dd492caf843
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Junyu Lai
2023-10-24 06:19:04 +00:00
committed by Automerger Merge Worker
13 changed files with 313 additions and 34 deletions

View File

@@ -95,6 +95,7 @@ android_library {
],
static_libs: [
"NetworkStackApiCurrentShims",
"net-utils-device-common-struct",
],
apex_available: ["com.android.tethering"],
lint: { strict_updatability_linting: true },
@@ -109,6 +110,7 @@ android_library {
],
static_libs: [
"NetworkStackApiStableShims",
"net-utils-device-common-struct",
],
apex_available: ["com.android.tethering"],
lint: { strict_updatability_linting: true },

View File

@@ -23,6 +23,7 @@ package {
// as the above target may not exist
// depending on the branch
// The library requires the final artifact to contain net-utils-device-common-struct.
java_library {
name: "connectivity-net-module-utils-bpf",
srcs: [
@@ -38,8 +39,9 @@ java_library {
libs: [
"androidx.annotation_annotation",
"framework-connectivity.stubs.module_lib",
],
static_libs: [
// For libraries which are statically linked in framework-connectivity, do not
// statically link here because callers of this library might already have a static
// version linked.
"net-utils-device-common-struct",
],
apex_available: [

View File

@@ -101,7 +101,7 @@ java_defaults {
"framework-connectivity-javastream-protos",
],
impl_only_static_libs: [
"net-utils-device-common-struct",
"net-utils-device-common-bpf",
],
libs: [
"androidx.annotation_annotation",
@@ -130,7 +130,7 @@ java_library {
// to generate the SDK stubs.
// Even if the library is included in "impl_only_static_libs" of defaults. This is still
// needed because java_library which doesn't understand "impl_only_static_libs".
"net-utils-device-common-struct",
"net-utils-device-common-bpf",
],
libs: [
// This cannot be in the defaults clause above because if it were, it would be used

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2023 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 android.net;
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
import static android.net.BpfNetMapsUtils.isFirewallAllowList;
import static android.net.BpfNetMapsUtils.throwIfPreT;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import android.annotation.NonNull;
import android.annotation.RequiresApi;
import android.os.Build;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
import android.system.Os;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
/**
* A helper class to *read* java BpfMaps.
* @hide
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
public class BpfNetMapsReader {
// Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
// BpfMap implementation.
// Bpf map to store various networking configurations, the format of the value is different
// for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
private final IBpfMap<S32, U32> mConfigurationMap;
// Bpf map to store per uid traffic control configurations.
// See {@link UidOwnerValue} for more detail.
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
private final Dependencies mDeps;
public BpfNetMapsReader() {
this(new Dependencies());
}
@VisibleForTesting
public BpfNetMapsReader(@NonNull Dependencies deps) {
if (!SdkLevel.isAtLeastT()) {
throw new UnsupportedOperationException(
BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
}
mDeps = deps;
mConfigurationMap = mDeps.getConfigurationMap();
mUidOwnerMap = mDeps.getUidOwnerMap();
}
/**
* Dependencies of BpfNetMapReader, for injection in tests.
*/
@VisibleForTesting
public static class Dependencies {
/** Get the configuration map. */
public IBpfMap<S32, U32> getConfigurationMap() {
try {
return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
S32.class, U32.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open configuration map", e);
}
}
/** Get the uid owner map. */
public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
S32.class, UidOwnerValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid owner map", e);
}
}
}
/**
* Get the specified firewall chain's status.
*
* @param chain target chain
* @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public boolean isChainEnabled(final int chain) {
return isChainEnabled(mConfigurationMap, chain);
}
/**
* Get firewall rule of specified firewall chain on specified uid.
*
* @param chain target chain
* @param uid target uid
* @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
* {@link ConnectivityManager#FIREWALL_RULE_DENY}.
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public int getUidRule(final int chain, final int uid) {
return getUidRule(mUidOwnerMap, chain, uid);
}
/**
* Get the specified firewall chain's status.
*
* @param configurationMap target configurationMap
* @param chain target chain
* @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public static boolean isChainEnabled(
final IBpfMap<Struct.S32, Struct.U32> configurationMap, final int chain) {
throwIfPreT("isChainEnabled is not available on pre-T devices");
final long match = getMatchByFirewallChain(chain);
try {
final Struct.U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
return (config.val & match) != 0;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
"Unable to get firewall chain status: " + Os.strerror(e.errno));
}
}
/**
* Get firewall rule of specified firewall chain on specified uid.
*
* @param uidOwnerMap target uidOwnerMap.
* @param chain target chain.
* @param uid target uid.
* @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public static int getUidRule(final IBpfMap<Struct.S32, UidOwnerValue> uidOwnerMap,
final int chain, final int uid) {
throwIfPreT("getUidRule is not available on pre-T devices");
final long match = getMatchByFirewallChain(chain);
final boolean isAllowList = isFirewallAllowList(chain);
try {
final UidOwnerValue uidMatch = uidOwnerMap.getValue(new Struct.S32(uid));
final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
"Unable to get uid rule status: " + Os.strerror(e.errno));
}
}
}

View File

@@ -39,6 +39,8 @@ import static android.system.OsConstants.EINVAL;
import android.os.ServiceSpecificException;
import android.util.Pair;
import com.android.modules.utils.build.SdkLevel;
import java.util.StringJoiner;
/**
@@ -124,4 +126,15 @@ public class BpfNetMapsUtils {
}
return sj.toString();
}
public static final boolean PRE_T = !SdkLevel.isAtLeastT();
/**
* Throw UnsupportedOperationException if SdkLevel is before T.
*/
public static void throwIfPreT(final String msg) {
if (PRE_T) {
throw new UnsupportedOperationException(msg);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 The Android Open Source Project
* Copyright (C) 2023 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.
@@ -14,11 +14,15 @@
* limitations under the License.
*/
package com.android.server;
package android.net;
import com.android.net.module.util.Struct;
/** Value type for per uid traffic control configuration map */
/**
* Value type for per uid traffic control configuration map.
*
* @hide
*/
public class UidOwnerValue extends Struct {
// Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask below.
@Field(order = 0, type = Type.S32)

View File

@@ -188,7 +188,6 @@ java_library {
"dnsresolver_aidl_interface-V11-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-bpf",
"net-utils-device-common-ip",
"net-utils-device-common-netlink",
"net-utils-services-common",

View File

@@ -26,6 +26,7 @@ import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
import static android.net.BpfNetMapsUtils.PRE_T;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
import static android.net.BpfNetMapsUtils.matchToString;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
@@ -51,7 +52,9 @@ import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
import android.app.StatsManager;
import android.content.Context;
import android.net.BpfNetMapsReader;
import android.net.INetd;
import android.net.UidOwnerValue;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -92,7 +95,6 @@ import java.util.StringJoiner;
* {@hide}
*/
public class BpfNetMaps {
private static final boolean PRE_T = !SdkLevel.isAtLeastT();
static {
if (!PRE_T) {
System.loadLibrary("service-connectivity");
@@ -298,6 +300,7 @@ public class BpfNetMaps {
}
/** Constructor used after T that doesn't need to use netd anymore. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public BpfNetMaps(final Context context) {
this(context, null);
@@ -420,6 +423,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void addNaughtyApp(final int uid) {
throwIfPreT("addNaughtyApp is not available on pre-T devices");
@@ -438,6 +442,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void removeNaughtyApp(final int uid) {
throwIfPreT("removeNaughtyApp is not available on pre-T devices");
@@ -456,6 +461,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void addNiceApp(final int uid) {
throwIfPreT("addNiceApp is not available on pre-T devices");
@@ -474,6 +480,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void removeNiceApp(final int uid) {
throwIfPreT("removeNiceApp is not available on pre-T devices");
@@ -494,6 +501,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setChildChain(final int childChain, final boolean enable) {
throwIfPreT("setChildChain is not available on pre-T devices");
@@ -523,18 +531,14 @@ public class BpfNetMaps {
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*
* @deprecated Use {@link BpfNetMapsReader#isChainEnabled} instead.
*/
// TODO: Migrate the callers to use {@link BpfNetMapsReader#isChainEnabled} instead.
@Deprecated
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public boolean isChainEnabled(final int childChain) {
throwIfPreT("isChainEnabled is not available on pre-T devices");
final long match = getMatchByFirewallChain(childChain);
try {
final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
return (config.val & match) != 0;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
"Unable to get firewall chain status: " + Os.strerror(e.errno));
}
return BpfNetMapsReader.isChainEnabled(sConfigurationMap, childChain);
}
private Set<Integer> asSet(final int[] uids) {
@@ -554,6 +558,7 @@ public class BpfNetMaps {
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws IllegalArgumentException if {@code chain} is not a valid chain.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void replaceUidChain(final int chain, final int[] uids) {
throwIfPreT("replaceUidChain is not available on pre-T devices");
@@ -638,6 +643,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setUidRule(final int childChain, final int uid, final int firewallRule) {
throwIfPreT("setUidRule is not available on pre-T devices");
@@ -667,20 +673,12 @@ public class BpfNetMaps {
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*
* @deprecated use {@link BpfNetMapsReader#getUidRule} instead.
*/
// TODO: Migrate the callers to use {@link BpfNetMapsReader#getUidRule} instead.
public int getUidRule(final int childChain, final int uid) {
throwIfPreT("isUidChainEnabled is not available on pre-T devices");
final long match = getMatchByFirewallChain(childChain);
final boolean isAllowList = isFirewallAllowList(childChain);
try {
final UidOwnerValue uidMatch = sUidOwnerMap.getValue(new S32(uid));
final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
"Unable to get uid rule status: " + Os.strerror(e.errno));
}
return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);
}
private Set<Integer> getUidsMatchEnabled(final int childChain) throws ErrnoException {
@@ -830,6 +828,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void updateUidLockdownRule(final int uid, final boolean add) {
throwIfPreT("updateUidLockdownRule is not available on pre-T devices");
@@ -852,6 +851,7 @@ public class BpfNetMaps {
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void swapActiveStatsMap() {
throwIfPreT("swapActiveStatsMap is not available on pre-T devices");
@@ -927,6 +927,7 @@ public class BpfNetMaps {
}
/** Register callback for statsd to pull atom. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setPullAtomCallback(final Context context) {
throwIfPreT("setPullAtomCallback is not available on pre-T devices");
@@ -1016,6 +1017,7 @@ public class BpfNetMaps {
* @throws IOException when file descriptor is invalid.
* @throws ServiceSpecificException when the method is called on an unsupported device.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
throws IOException, ServiceSpecificException {
if (PRE_T) {

View File

@@ -181,6 +181,8 @@ java_library {
},
}
// The net-utils-device-common-netlink library requires the callers to contain
// net-utils-device-common-struct.
java_library {
name: "net-utils-device-common-netlink",
srcs: [
@@ -192,12 +194,13 @@ java_library {
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/NetworkStack:__subpackages__",
],
static_libs: [
"net-utils-device-common-struct",
],
libs: [
"androidx.annotation_annotation",
"framework-connectivity.stubs.module_lib",
// For libraries which are statically linked in framework-connectivity, do not
// statically link here because callers of this library might already have a static
// version linked.
"net-utils-device-common-struct",
],
apex_available: [
"com.android.tethering",
@@ -209,6 +212,8 @@ java_library {
},
}
// The net-utils-device-common-ip library requires the callers to contain
// net-utils-device-common-struct.
java_library {
// TODO : this target should probably be folded into net-utils-device-common
name: "net-utils-device-common-ip",

View File

@@ -38,6 +38,7 @@ java_library {
"net-utils-device-common",
"net-utils-device-common-async",
"net-utils-device-common-netlink",
"net-utils-device-common-struct",
"net-utils-device-common-wear",
"modules-utils-build_system",
],

View File

@@ -43,6 +43,8 @@ import java.util.concurrent.ConcurrentHashMap;
public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>();
public TestBpfMap() {}
// TODO: Remove this constructor
public TestBpfMap(final Class<K> key, final Class<V> value) {
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 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 android.net
import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY
import android.net.BpfNetMapsUtils.getMatchByFirewallChain
import android.os.Build
import com.android.net.module.util.IBpfMap
import com.android.net.module.util.Struct.S32
import com.android.net.module.util.Struct.U32
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.TestBpfMap
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
// pre-T devices does not support Bpf.
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class BpfNetMapsReaderTest {
private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap()
private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap()
private val bpfNetMapsReader = BpfNetMapsReader(
TestDependencies(testConfigurationMap, testUidOwnerMap))
class TestDependencies(
private val configMap: IBpfMap<S32, U32>,
private val uidOwnerMap: IBpfMap<S32, UidOwnerValue>
) : BpfNetMapsReader.Dependencies() {
override fun getConfigurationMap() = configMap
override fun getUidOwnerMap() = uidOwnerMap
}
private fun doTestIsChainEnabled(chain: Int) {
testConfigurationMap.updateEntry(
UID_RULES_CONFIGURATION_KEY,
U32(getMatchByFirewallChain(chain))
)
assertTrue(bpfNetMapsReader.isChainEnabled(chain))
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
assertFalse(bpfNetMapsReader.isChainEnabled(chain))
}
@Test
@Throws(Exception::class)
fun testIsChainEnabled() {
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE)
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY)
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE)
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_RESTRICTED)
doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY)
}
}

View File

@@ -66,6 +66,7 @@ import android.app.StatsManager;
import android.content.Context;
import android.net.BpfNetMapsUtils;
import android.net.INetd;
import android.net.UidOwnerValue;
import android.os.Build;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;