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