1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net 18 19 import android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED 20 import android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED 21 import android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY 22 import android.net.BpfNetMapsConstants.DOZABLE_MATCH 23 import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH 24 import android.net.BpfNetMapsConstants.PENALTY_BOX_ADMIN_MATCH 25 import android.net.BpfNetMapsConstants.PENALTY_BOX_USER_MATCH 26 import android.net.BpfNetMapsConstants.STANDBY_MATCH 27 import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY 28 import android.net.BpfNetMapsUtils.getMatchByFirewallChain 29 import android.os.Build.VERSION_CODES 30 import android.os.Process.FIRST_APPLICATION_UID 31 import com.android.net.module.util.IBpfMap 32 import com.android.net.module.util.Struct.S32 33 import com.android.net.module.util.Struct.U32 34 import com.android.net.module.util.Struct.U8 35 import com.android.testutils.DevSdkIgnoreRule 36 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo 37 import com.android.testutils.DevSdkIgnoreRunner 38 import com.android.testutils.TestBpfMap 39 import java.lang.reflect.Modifier 40 import kotlin.test.assertEquals 41 import kotlin.test.assertFalse 42 import kotlin.test.assertTrue 43 import org.junit.Rule 44 import org.junit.Test 45 import org.junit.runner.RunWith 46 47 private const val TEST_UID1 = 11234 48 private const val TEST_UID2 = TEST_UID1 + 1 49 private const val TEST_UID3 = TEST_UID2 + 1 50 private const val NO_IIF = 0 51 52 // NetworkStack can not use this before U due to b/326143935 53 @RunWith(DevSdkIgnoreRunner::class) 54 @IgnoreUpTo(VERSION_CODES.TIRAMISU) 55 class NetworkStackBpfNetMapsTest { 56 @Rule 57 @JvmField 58 val ignoreRule = DevSdkIgnoreRule() 59 60 private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap() 61 private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap() 62 private val testDataSaverEnabledMap: IBpfMap<S32, U8> = TestBpfMap() 63 private val bpfNetMapsReader = NetworkStackBpfNetMaps( 64 TestDependencies(testConfigurationMap, testUidOwnerMap, testDataSaverEnabledMap) 65 ) 66 67 class TestDependencies( 68 private val configMap: IBpfMap<S32, U32>, 69 private val uidOwnerMap: IBpfMap<S32, UidOwnerValue>, 70 private val dataSaverEnabledMap: IBpfMap<S32, U8> 71 ) : NetworkStackBpfNetMaps.Dependencies() { getConfigurationMapnull72 override fun getConfigurationMap() = configMap 73 override fun getUidOwnerMap() = uidOwnerMap 74 override fun getDataSaverEnabledMap() = dataSaverEnabledMap 75 } 76 77 private fun doTestIsChainEnabled(chain: Int) { 78 testConfigurationMap.updateEntry( 79 UID_RULES_CONFIGURATION_KEY, 80 U32(getMatchByFirewallChain(chain)) 81 ) 82 assertTrue(bpfNetMapsReader.isChainEnabled(chain)) 83 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) 84 assertFalse(bpfNetMapsReader.isChainEnabled(chain)) 85 } 86 87 @Test 88 @Throws(Exception::class) testIsChainEnablednull89 fun testIsChainEnabled() { 90 doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE) 91 doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY) 92 doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE) 93 doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_RESTRICTED) 94 doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY) 95 } 96 97 @Test testFirewallChainListnull98 fun testFirewallChainList() { 99 // Verify that when a firewall chain constant is added, it should also be included in 100 // firewall chain list. 101 val declaredChains = ConnectivityManager::class.java.declaredFields.filter { 102 Modifier.isStatic(it.modifiers) && it.name.startsWith("FIREWALL_CHAIN_") 103 } 104 // Verify the size matches, this also verifies no common item in allow and deny chains. 105 assertEquals( 106 BpfNetMapsConstants.ALLOW_CHAINS.size + 107 BpfNetMapsConstants.DENY_CHAINS.size + 108 BpfNetMapsConstants.METERED_ALLOW_CHAINS.size + 109 BpfNetMapsConstants.METERED_DENY_CHAINS.size, 110 declaredChains.size 111 ) 112 declaredChains.forEach { 113 assertTrue( 114 BpfNetMapsConstants.ALLOW_CHAINS.contains(it.get(null)) || 115 BpfNetMapsConstants.METERED_ALLOW_CHAINS.contains(it.get(null)) || 116 BpfNetMapsConstants.DENY_CHAINS.contains(it.get(null)) || 117 BpfNetMapsConstants.METERED_DENY_CHAINS.contains(it.get(null)) 118 ) 119 } 120 } 121 mockChainEnablednull122 private fun mockChainEnabled(chain: Int, enabled: Boolean) { 123 val config = testConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).`val` 124 val newConfig = if (enabled) { 125 config or getMatchByFirewallChain(chain) 126 } else { 127 config and getMatchByFirewallChain(chain).inv() 128 } 129 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(newConfig)) 130 } 131 mockDataSaverEnablednull132 private fun mockDataSaverEnabled(enabled: Boolean) { 133 val dataSaverValue = if (enabled) {DATA_SAVER_ENABLED} else {DATA_SAVER_DISABLED} 134 testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(dataSaverValue)) 135 } 136 isUidNetworkingBlockednull137 fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false) = 138 bpfNetMapsReader.isUidNetworkingBlocked(uid, metered) 139 140 @Test 141 fun testIsUidNetworkingBlockedByFirewallChains_allowChain() { 142 mockDataSaverEnabled(enabled = false) 143 // With everything disabled by default, verify the return value is false. 144 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) 145 assertFalse(isUidNetworkingBlocked(TEST_UID1)) 146 147 // Enable dozable chain but does not provide allowed list. Verify the network is blocked 148 // for all uids. 149 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true) 150 assertTrue(isUidNetworkingBlocked(TEST_UID1)) 151 assertTrue(isUidNetworkingBlocked(TEST_UID2)) 152 153 // Add uid1 to dozable allowed list. Verify the network is not blocked for uid1, while 154 // uid2 is blocked. 155 testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, DOZABLE_MATCH)) 156 assertFalse(isUidNetworkingBlocked(TEST_UID1)) 157 assertTrue(isUidNetworkingBlocked(TEST_UID2)) 158 } 159 160 @Test testIsUidNetworkingBlockedByFirewallChains_denyChainnull161 fun testIsUidNetworkingBlockedByFirewallChains_denyChain() { 162 mockDataSaverEnabled(enabled = false) 163 // Enable standby chain but does not provide denied list. Verify the network is allowed 164 // for all uids. 165 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) 166 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true) 167 assertFalse(isUidNetworkingBlocked(TEST_UID1)) 168 assertFalse(isUidNetworkingBlocked(TEST_UID2)) 169 170 // Add uid1 to standby allowed list. Verify the network is blocked for uid1, while 171 // uid2 is not blocked. 172 testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, STANDBY_MATCH)) 173 assertTrue(isUidNetworkingBlocked(TEST_UID1)) 174 assertFalse(isUidNetworkingBlocked(TEST_UID2)) 175 } 176 177 @Test testIsUidNetworkingBlockedByFirewallChains_blockedWithAllowednull178 fun testIsUidNetworkingBlockedByFirewallChains_blockedWithAllowed() { 179 // Uids blocked by powersave chain but allowed by standby chain, verify the blocking 180 // takes higher priority. 181 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) 182 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE, true) 183 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true) 184 mockDataSaverEnabled(enabled = false) 185 assertTrue(isUidNetworkingBlocked(TEST_UID1)) 186 } 187 188 @IgnoreUpTo(VERSION_CODES.S_V2) 189 @Test testIsUidNetworkingBlockedByDataSavernull190 fun testIsUidNetworkingBlockedByDataSaver() { 191 mockDataSaverEnabled(enabled = false) 192 // With everything disabled by default, verify the return value is false. 193 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) 194 assertFalse(isUidNetworkingBlocked(TEST_UID1, metered = true)) 195 196 // Add uid1 to penalty box, verify the network is blocked for uid1, while uid2 is not 197 // affected. 198 testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH)) 199 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 200 assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) 201 testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_ADMIN_MATCH)) 202 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 203 assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) 204 testUidOwnerMap.updateEntry( 205 S32(TEST_UID1), 206 UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH or PENALTY_BOX_ADMIN_MATCH) 207 ) 208 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 209 assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) 210 211 // Enable data saver, verify the network is blocked for uid1, uid2, but uid3 in happy box 212 // is not affected. 213 mockDataSaverEnabled(enabled = true) 214 testUidOwnerMap.updateEntry(S32(TEST_UID3), UidOwnerValue(NO_IIF, HAPPY_BOX_MATCH)) 215 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 216 assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) 217 assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) 218 219 // Add uid1 to happy box as well, verify nothing is changed because penalty box has higher 220 // priority. 221 testUidOwnerMap.updateEntry( 222 S32(TEST_UID1), 223 UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH or HAPPY_BOX_MATCH) 224 ) 225 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 226 assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) 227 assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) 228 testUidOwnerMap.updateEntry( 229 S32(TEST_UID1), 230 UidOwnerValue(NO_IIF, PENALTY_BOX_ADMIN_MATCH or HAPPY_BOX_MATCH) 231 ) 232 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 233 assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) 234 assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) 235 236 // Enable doze mode, verify uid3 is blocked even if it is in happy box. 237 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true) 238 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 239 assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true)) 240 assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true)) 241 242 // Disable doze mode and data saver, only uid1 which is in penalty box is blocked. 243 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, false) 244 mockDataSaverEnabled(enabled = false) 245 assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true)) 246 assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true)) 247 assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true)) 248 249 // Make the network non-metered, nothing is blocked. 250 assertFalse(isUidNetworkingBlocked(TEST_UID1)) 251 assertFalse(isUidNetworkingBlocked(TEST_UID2)) 252 assertFalse(isUidNetworkingBlocked(TEST_UID3)) 253 } 254 255 @Test testIsUidNetworkingBlocked_SystemUidnull256 fun testIsUidNetworkingBlocked_SystemUid() { 257 mockDataSaverEnabled(enabled = false) 258 testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0)) 259 mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true) 260 261 for (uid in FIRST_APPLICATION_UID - 5..FIRST_APPLICATION_UID + 5) { 262 // system uid is not blocked regardless of firewall chains 263 val expectBlocked = uid >= FIRST_APPLICATION_UID 264 testUidOwnerMap.updateEntry(S32(uid), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH)) 265 assertEquals( 266 expectBlocked, 267 isUidNetworkingBlocked(uid, metered = true), 268 "isUidNetworkingBlocked returns unexpected value for uid = " + uid 269 ) 270 } 271 } 272 273 @Test testGetDataSaverEnablednull274 fun testGetDataSaverEnabled() { 275 testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(DATA_SAVER_DISABLED)) 276 assertFalse(bpfNetMapsReader.dataSaverEnabled) 277 testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(DATA_SAVER_ENABLED)) 278 assertTrue(bpfNetMapsReader.dataSaverEnabled) 279 } 280 } 281