diff --git a/framework/src/android/net/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java index 37c58f079d..4ab6d3e0b6 100644 --- a/framework/src/android/net/BpfNetMapsReader.java +++ b/framework/src/android/net/BpfNetMapsReader.java @@ -17,6 +17,9 @@ package android.net; 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.PENALTY_BOX_MATCH; import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH; @@ -33,14 +36,15 @@ import android.os.Build; import android.os.ServiceSpecificException; import android.system.ErrnoException; import android.system.Os; +import android.util.Log; 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; +import com.android.net.module.util.Struct.U8; /** * 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 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 // BpfMap implementation. @@ -57,6 +63,7 @@ public class BpfNetMapsReader { // Bpf map to store per uid traffic control configurations. // See {@link UidOwnerValue} for more detail. private final IBpfMap mUidOwnerMap; + private final IBpfMap mDataSaverEnabledMap; private final Dependencies mDeps; // Bitmaps for calculating whether a given uid is blocked by firewall chains. @@ -104,6 +111,7 @@ public class BpfNetMapsReader { mDeps = deps; mConfigurationMap = mDeps.getConfigurationMap(); mUidOwnerMap = mDeps.getUidOwnerMap(); + mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap(); } /** @@ -130,6 +138,16 @@ public class BpfNetMapsReader { throw new IllegalStateException("Cannot open uid owner map", e); } } + + /** Get the data saver enabled map. */ + public IBpfMap 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. */ public static boolean isChainEnabled( - final IBpfMap configurationMap, final int chain) { + final IBpfMap 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); + final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY); return (config.val & match) != 0; } catch (ErrnoException e) { throw new ServiceSpecificException(e.errno, @@ -195,14 +213,14 @@ public class BpfNetMapsReader { * @throws ServiceSpecificException in case of failure, with an error code indicating the * cause of the failure. */ - public static int getUidRule(final IBpfMap uidOwnerMap, + public static int getUidRule(final IBpfMap 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 UidOwnerValue uidMatch = uidOwnerMap.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) { @@ -249,4 +267,29 @@ public class BpfNetMapsReader { if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false; 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)); + } + } } diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 57ecf4968a..a934ddb0bc 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -83,6 +83,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import libcore.net.event.NetworkEventDispatcher; @@ -6371,10 +6372,13 @@ public class ConnectivityManager { final BpfNetMapsReader reader = BpfNetMapsReader.getInstance(); final boolean isDataSaverEnabled; - // TODO: For U-QPR3+ devices, get data saver status from bpf configuration map directly. - final DataSaverStatusTracker dataSaverStatusTracker = - DataSaverStatusTracker.getInstance(mContext); - isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled(); + if (SdkLevel.isAtLeastV()) { + isDataSaverEnabled = reader.getDataSaverEnabled(); + } else { + final DataSaverStatusTracker dataSaverStatusTracker = + DataSaverStatusTracker.getInstance(mContext); + isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled(); + } return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled); } diff --git a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt index 258e4224f4..9de7f4dc7c 100644 --- a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt +++ b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt @@ -16,6 +16,9 @@ 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.HAPPY_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.Struct.S32 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.DevSdkIgnoreRunner import com.android.testutils.TestBpfMap @@ -33,6 +38,7 @@ import java.lang.reflect.Modifier import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -45,17 +51,24 @@ private const val NO_IIF = 0 @RunWith(DevSdkIgnoreRunner::class) @IgnoreUpTo(VERSION_CODES.S_V2) class BpfNetMapsReaderTest { + @Rule + @JvmField + val ignoreRule = DevSdkIgnoreRule() + private val testConfigurationMap: IBpfMap = TestBpfMap() private val testUidOwnerMap: IBpfMap = TestBpfMap() + private val testDataSaverEnabledMap: IBpfMap = TestBpfMap() private val bpfNetMapsReader = BpfNetMapsReader( - TestDependencies(testConfigurationMap, testUidOwnerMap)) + TestDependencies(testConfigurationMap, testUidOwnerMap, testDataSaverEnabledMap)) class TestDependencies( private val configMap: IBpfMap, - private val uidOwnerMap: IBpfMap + private val uidOwnerMap: IBpfMap, + private val dataSaverEnabledMap: IBpfMap ) : BpfNetMapsReader.Dependencies() { override fun getConfigurationMap() = configMap override fun getUidOwnerMap() = uidOwnerMap + override fun getDataSaverEnabledMap() = dataSaverEnabledMap } private fun doTestIsChainEnabled(chain: Int) { @@ -199,4 +212,13 @@ class BpfNetMapsReaderTest { assertFalse(isUidNetworkingBlocked(TEST_UID2)) 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) + } } diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java index b8c5447779..0082af2469 100644 --- a/tests/unit/java/android/net/ConnectivityManagerTest.java +++ b/tests/unit/java/android/net/ConnectivityManagerTest.java @@ -90,6 +90,7 @@ import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -102,6 +103,8 @@ import java.lang.ref.WeakReference; @SmallTest @DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R) public class ConnectivityManagerTest { + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); private static final int TIMEOUT_MS = 30_000; private static final int SHORT_TIMEOUT_MS = 150; @@ -524,6 +527,7 @@ public class ConnectivityManagerTest { + " attempts", ref.get()); } + @DevSdkIgnoreRule.IgnoreAfter(VERSION_CODES.UPSIDE_DOWN_CAKE) @Test public void testDataSaverStatusTracker() { mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm);