[BR11] Read data saver status from bpf

Writing data saver status to bpf is supported on Android V or
later devices. Thus, read that from bpf if available.

Test: atest FrameworksNetTests:android.net.connectivity.android.net.BpfNetMapsReaderTest
Test: atest ConnectivityCoverageTests:android.net.connectivity.android.net.ConnectivityManagerTest
Fix: 310801259
Change-Id: Ibd2616328d83f72ee6d2665239c3a44379d1ebf5
This commit is contained in:
Junyu Lai
2023-11-14 15:44:37 +08:00
parent 8b61783b78
commit 9e88052fe7
4 changed files with 84 additions and 11 deletions

View File

@@ -17,6 +17,9 @@
package android.net; package android.net;
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH; import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH; import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH; import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH; import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
@@ -33,14 +36,15 @@ import android.os.Build;
import android.os.ServiceSpecificException; import android.os.ServiceSpecificException;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel; import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap; import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap; 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.S32;
import com.android.net.module.util.Struct.U32; import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
/** /**
* A helper class to *read* java BpfMaps. * A helper class to *read* java BpfMaps.
@@ -48,6 +52,8 @@ import com.android.net.module.util.Struct.U32;
*/ */
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T @RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
public class BpfNetMapsReader { public class BpfNetMapsReader {
private static final String TAG = BpfNetMapsReader.class.getSimpleName();
// Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
// BpfMap implementation. // BpfMap implementation.
@@ -57,6 +63,7 @@ public class BpfNetMapsReader {
// Bpf map to store per uid traffic control configurations. // Bpf map to store per uid traffic control configurations.
// See {@link UidOwnerValue} for more detail. // See {@link UidOwnerValue} for more detail.
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap; private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
private final IBpfMap<S32, U8> mDataSaverEnabledMap;
private final Dependencies mDeps; private final Dependencies mDeps;
// Bitmaps for calculating whether a given uid is blocked by firewall chains. // Bitmaps for calculating whether a given uid is blocked by firewall chains.
@@ -104,6 +111,7 @@ public class BpfNetMapsReader {
mDeps = deps; mDeps = deps;
mConfigurationMap = mDeps.getConfigurationMap(); mConfigurationMap = mDeps.getConfigurationMap();
mUidOwnerMap = mDeps.getUidOwnerMap(); mUidOwnerMap = mDeps.getUidOwnerMap();
mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap();
} }
/** /**
@@ -130,6 +138,16 @@ public class BpfNetMapsReader {
throw new IllegalStateException("Cannot open uid owner map", e); throw new IllegalStateException("Cannot open uid owner map", e);
} }
} }
/** Get the data saver enabled map. */
public IBpfMap<S32, U8> getDataSaverEnabledMap() {
try {
return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class,
U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open data saver enabled map", e);
}
}
} }
/** /**
@@ -171,12 +189,12 @@ public class BpfNetMapsReader {
* cause of the failure. * cause of the failure.
*/ */
public static boolean isChainEnabled( public static boolean isChainEnabled(
final IBpfMap<Struct.S32, Struct.U32> configurationMap, final int chain) { final IBpfMap<S32, U32> configurationMap, final int chain) {
throwIfPreT("isChainEnabled is not available on pre-T devices"); throwIfPreT("isChainEnabled is not available on pre-T devices");
final long match = getMatchByFirewallChain(chain); final long match = getMatchByFirewallChain(chain);
try { try {
final Struct.U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY); final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
return (config.val & match) != 0; return (config.val & match) != 0;
} catch (ErrnoException e) { } catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno, throw new ServiceSpecificException(e.errno,
@@ -195,14 +213,14 @@ public class BpfNetMapsReader {
* @throws ServiceSpecificException in case of failure, with an error code indicating the * @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure. * cause of the failure.
*/ */
public static int getUidRule(final IBpfMap<Struct.S32, UidOwnerValue> uidOwnerMap, public static int getUidRule(final IBpfMap<S32, UidOwnerValue> uidOwnerMap,
final int chain, final int uid) { final int chain, final int uid) {
throwIfPreT("getUidRule is not available on pre-T devices"); throwIfPreT("getUidRule is not available on pre-T devices");
final long match = getMatchByFirewallChain(chain); final long match = getMatchByFirewallChain(chain);
final boolean isAllowList = isFirewallAllowList(chain); final boolean isAllowList = isFirewallAllowList(chain);
try { try {
final UidOwnerValue uidMatch = uidOwnerMap.getValue(new Struct.S32(uid)); final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid));
final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0; final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY; return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
} catch (ErrnoException e) { } catch (ErrnoException e) {
@@ -249,4 +267,29 @@ public class BpfNetMapsReader {
if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false; if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
return isDataSaverEnabled; return isDataSaverEnabled;
} }
/**
* Get Data Saver enabled or disabled
*
* @return whether Data Saver is enabled or disabled.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public boolean getDataSaverEnabled() {
throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
// Note that this is not expected to be called until V given that it relies on the
// counterpart platform solution to set data saver status to bpf.
// See {@code NetworkManagementService#setDataSaverModeEnabled}.
if (!SdkLevel.isAtLeastV()) {
Log.wtf(TAG, "getDataSaverEnabled is not expected to be called on pre-V devices");
}
try {
return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno, "Unable to get data saver: "
+ Os.strerror(e.errno));
}
}
} }

View File

@@ -83,6 +83,7 @@ import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import libcore.net.event.NetworkEventDispatcher; import libcore.net.event.NetworkEventDispatcher;
@@ -6371,10 +6372,13 @@ public class ConnectivityManager {
final BpfNetMapsReader reader = BpfNetMapsReader.getInstance(); final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
final boolean isDataSaverEnabled; final boolean isDataSaverEnabled;
// TODO: For U-QPR3+ devices, get data saver status from bpf configuration map directly. if (SdkLevel.isAtLeastV()) {
final DataSaverStatusTracker dataSaverStatusTracker = isDataSaverEnabled = reader.getDataSaverEnabled();
DataSaverStatusTracker.getInstance(mContext); } else {
isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled(); final DataSaverStatusTracker dataSaverStatusTracker =
DataSaverStatusTracker.getInstance(mContext);
isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled();
}
return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled); return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled);
} }

View File

@@ -16,6 +16,9 @@
package android.net package android.net
import android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED
import android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED
import android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY
import android.net.BpfNetMapsConstants.DOZABLE_MATCH import android.net.BpfNetMapsConstants.DOZABLE_MATCH
import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH
import android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH import android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH
@@ -26,6 +29,8 @@ import android.os.Build.VERSION_CODES
import com.android.net.module.util.IBpfMap import com.android.net.module.util.IBpfMap
import com.android.net.module.util.Struct.S32 import com.android.net.module.util.Struct.S32
import com.android.net.module.util.Struct.U32 import com.android.net.module.util.Struct.U32
import com.android.net.module.util.Struct.U8
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.TestBpfMap import com.android.testutils.TestBpfMap
@@ -33,6 +38,7 @@ import java.lang.reflect.Modifier
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -45,17 +51,24 @@ private const val NO_IIF = 0
@RunWith(DevSdkIgnoreRunner::class) @RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(VERSION_CODES.S_V2) @IgnoreUpTo(VERSION_CODES.S_V2)
class BpfNetMapsReaderTest { class BpfNetMapsReaderTest {
@Rule
@JvmField
val ignoreRule = DevSdkIgnoreRule()
private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap() private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap()
private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap() private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap()
private val testDataSaverEnabledMap: IBpfMap<S32, U8> = TestBpfMap()
private val bpfNetMapsReader = BpfNetMapsReader( private val bpfNetMapsReader = BpfNetMapsReader(
TestDependencies(testConfigurationMap, testUidOwnerMap)) TestDependencies(testConfigurationMap, testUidOwnerMap, testDataSaverEnabledMap))
class TestDependencies( class TestDependencies(
private val configMap: IBpfMap<S32, U32>, private val configMap: IBpfMap<S32, U32>,
private val uidOwnerMap: IBpfMap<S32, UidOwnerValue> private val uidOwnerMap: IBpfMap<S32, UidOwnerValue>,
private val dataSaverEnabledMap: IBpfMap<S32, U8>
) : BpfNetMapsReader.Dependencies() { ) : BpfNetMapsReader.Dependencies() {
override fun getConfigurationMap() = configMap override fun getConfigurationMap() = configMap
override fun getUidOwnerMap() = uidOwnerMap override fun getUidOwnerMap() = uidOwnerMap
override fun getDataSaverEnabledMap() = dataSaverEnabledMap
} }
private fun doTestIsChainEnabled(chain: Int) { private fun doTestIsChainEnabled(chain: Int) {
@@ -199,4 +212,13 @@ class BpfNetMapsReaderTest {
assertFalse(isUidNetworkingBlocked(TEST_UID2)) assertFalse(isUidNetworkingBlocked(TEST_UID2))
assertFalse(isUidNetworkingBlocked(TEST_UID3)) assertFalse(isUidNetworkingBlocked(TEST_UID3))
} }
@IgnoreUpTo(VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testGetDataSaverEnabled() {
testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(DATA_SAVER_DISABLED))
assertFalse(bpfNetMapsReader.dataSaverEnabled)
testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(DATA_SAVER_ENABLED))
assertTrue(bpfNetMapsReader.dataSaverEnabled)
}
} }

View File

@@ -90,6 +90,7 @@ import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner; import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -102,6 +103,8 @@ import java.lang.ref.WeakReference;
@SmallTest @SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R) @DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
public class ConnectivityManagerTest { public class ConnectivityManagerTest {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private static final int TIMEOUT_MS = 30_000; private static final int TIMEOUT_MS = 30_000;
private static final int SHORT_TIMEOUT_MS = 150; private static final int SHORT_TIMEOUT_MS = 150;
@@ -524,6 +527,7 @@ public class ConnectivityManagerTest {
+ " attempts", ref.get()); + " attempts", ref.get());
} }
@DevSdkIgnoreRule.IgnoreAfter(VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test @Test
public void testDataSaverStatusTracker() { public void testDataSaverStatusTracker() {
mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm); mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm);