/* * 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.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_ADMIN_MATCH import android.net.BpfNetMapsConstants.PENALTY_BOX_USER_MATCH import android.net.BpfNetMapsConstants.STANDBY_MATCH import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY import android.net.BpfNetMapsUtils.getMatchByFirewallChain import android.os.Build.VERSION_CODES import android.os.Process.FIRST_APPLICATION_UID 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 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 private const val TEST_UID1 = 11234 private const val TEST_UID2 = TEST_UID1 + 1 private const val TEST_UID3 = TEST_UID2 + 1 private const val NO_IIF = 0 // NetworkStack can not use this before U due to b/326143935 @RunWith(DevSdkIgnoreRunner::class) @IgnoreUpTo(VERSION_CODES.TIRAMISU) class NetworkStackBpfNetMapsTest { @Rule @JvmField val ignoreRule = DevSdkIgnoreRule() private val testConfigurationMap: IBpfMap = TestBpfMap() private val testUidOwnerMap: IBpfMap = TestBpfMap() private val testDataSaverEnabledMap: IBpfMap = TestBpfMap() private val bpfNetMapsReader = NetworkStackBpfNetMaps( TestDependencies(testConfigurationMap, testUidOwnerMap, testDataSaverEnabledMap) ) class TestDependencies( private val configMap: IBpfMap, private val uidOwnerMap: IBpfMap, private val dataSaverEnabledMap: IBpfMap ) : NetworkStackBpfNetMaps.Dependencies() { override fun getConfigurationMap() = configMap override fun getUidOwnerMap() = uidOwnerMap override fun getDataSaverEnabledMap() = dataSaverEnabledMap } 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) } @Test fun testFirewallChainList() { // Verify that when a firewall chain constant is added, it should also be included in // firewall chain list. val declaredChains = ConnectivityManager::class.java.declaredFields.filter { Modifier.isStatic(it.modifiers) && it.name.startsWith("FIREWALL_CHAIN_") } // Verify the size matches, this also verifies no common item in allow and deny chains. assertEquals( BpfNetMapsConstants.ALLOW_CHAINS.size + BpfNetMapsConstants.DENY_CHAINS.size + BpfNetMapsConstants.METERED_ALLOW_CHAINS.size + BpfNetMapsConstants.METERED_DENY_CHAINS.size, declaredChains.size ) declaredChains.forEach { assertTrue( BpfNetMapsConstants.ALLOW_CHAINS.contains(it.get(null)) || BpfNetMapsConstants.METERED_ALLOW_CHAINS.contains(it.get(null)) || BpfNetMapsConstants.DENY_CHAINS.contains(it.get(null)) || BpfNetMapsConstants.METERED_DENY_CHAINS.contains(it.get(null)) ) } } private fun mockChainEnabled(chain: Int, enabled: Boolean) { val config = testConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).`val` val newConfig = if (enabled) { config or getMatchByFirewallChain(chain) } else { config and getMatchByFirewallChain(chain).inv() } testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(newConfig)) } private fun mockDataSaverEnabled(enabled: Boolean) { val dataSaverValue = if (enabled) {DATA_SAVER_ENABLED} else {DATA_SAVER_DISABLED} testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(dataSaverValue)) } fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false) = bpfNetMapsReader.isUidNetworkingBlocked(uid, metered) @Test fun testIsUidNetworkingBlockedByFirewallChains_allowChain() { mockDataSaverEnabled(enabled = false) // With everything disabled by default, verify the return value is false. testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) assertFalse(isUidNetworkingBlocked(TEST_UID1)) // Enable dozable chain but does not provide allowed list. Verify the network is blocked // for all uids. mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true) assertTrue(isUidNetworkingBlocked(TEST_UID1)) assertTrue(isUidNetworkingBlocked(TEST_UID2)) // Add uid1 to dozable allowed list. Verify the network is not blocked for uid1, while // uid2 is blocked. testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, DOZABLE_MATCH)) assertFalse(isUidNetworkingBlocked(TEST_UID1)) assertTrue(isUidNetworkingBlocked(TEST_UID2)) } @Test fun testIsUidNetworkingBlockedByFirewallChains_denyChain() { mockDataSaverEnabled(enabled = false) // Enable standby chain but does not provide denied list. Verify the network is allowed // for all uids. testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true) assertFalse(isUidNetworkingBlocked(TEST_UID1)) assertFalse(isUidNetworkingBlocked(TEST_UID2)) // Add uid1 to standby allowed list. Verify the network is blocked for uid1, while // uid2 is not blocked. testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, STANDBY_MATCH)) assertTrue(isUidNetworkingBlocked(TEST_UID1)) assertFalse(isUidNetworkingBlocked(TEST_UID2)) } @Test fun testIsUidNetworkingBlockedByFirewallChains_blockedWithAllowed() { // Uids blocked by powersave chain but allowed by standby chain, verify the blocking // takes higher priority. testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE, true) mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true) mockDataSaverEnabled(enabled = false) assertTrue(isUidNetworkingBlocked(TEST_UID1)) } @IgnoreUpTo(VERSION_CODES.S_V2) @Test fun testIsUidNetworkingBlockedByDataSaver() { mockDataSaverEnabled(enabled = false) // With everything disabled by default, verify the return value is false. testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) assertFalse(isUidNetworkingBlocked(TEST_UID1, metered = true)) // Add uid1 to penalty box, verify the network is blocked for uid1, while uid2 is not // affected. testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH)) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_ADMIN_MATCH)) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) testUidOwnerMap.updateEntry( S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH or PENALTY_BOX_ADMIN_MATCH) ) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) // Enable data saver, verify the network is blocked for uid1, uid2, but uid3 in happy box // is not affected. mockDataSaverEnabled(enabled = true) testUidOwnerMap.updateEntry(S32(TEST_UID3), UidOwnerValue(NO_IIF, HAPPY_BOX_MATCH)) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) // Add uid1 to happy box as well, verify nothing is changed because penalty box has higher // priority. testUidOwnerMap.updateEntry( S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH or HAPPY_BOX_MATCH) ) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) testUidOwnerMap.updateEntry( S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_ADMIN_MATCH or HAPPY_BOX_MATCH) ) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) // Enable doze mode, verify uid3 is blocked even if it is in happy box. mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true)) // Disable doze mode and data saver, only uid1 which is in penalty box is blocked. mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, false) mockDataSaverEnabled(enabled = false) assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) // Make the network non-metered, nothing is blocked. assertFalse(isUidNetworkingBlocked(TEST_UID1)) assertFalse(isUidNetworkingBlocked(TEST_UID2)) assertFalse(isUidNetworkingBlocked(TEST_UID3)) } @Test fun testIsUidNetworkingBlocked_SystemUid() { mockDataSaverEnabled(enabled = false) testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true) for (uid in FIRST_APPLICATION_UID - 5..FIRST_APPLICATION_UID + 5) { // system uid is not blocked regardless of firewall chains val expectBlocked = uid >= FIRST_APPLICATION_UID testUidOwnerMap.updateEntry(S32(uid), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH)) assertEquals( expectBlocked, isUidNetworkingBlocked(uid, metered = true), "isUidNetworkingBlocked returns unexpected value for uid = " + uid ) } } @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) } }