1 /*
2  * Copyright (C) 2024 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 com.android.server
18 
19 import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
20 import android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED
21 import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
22 import android.net.ConnectivityManager.BLOCKED_REASON_DOZE
23 import android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED
24 import android.net.ConnectivityManager.BLOCKED_REASON_NONE
25 import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
26 import android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE
27 import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER
28 import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
29 import android.net.ConnectivityManager.FIREWALL_RULE_DENY
30 import android.net.ConnectivitySettingsManager
31 import android.net.INetd.PERMISSION_NONE
32 import android.net.Network
33 import android.net.NetworkCapabilities
34 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
35 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
36 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
37 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
38 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
39 import android.net.NetworkCapabilities.TRANSPORT_WIFI
40 import android.net.NetworkRequest
41 import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
42 import android.os.Build
43 import android.os.Process
44 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
45 import com.android.testutils.DevSdkIgnoreRunner
46 import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt
47 import com.android.testutils.TestableNetworkCallback
48 import org.junit.Test
49 import org.junit.runner.RunWith
50 import org.mockito.ArgumentMatchers.anyBoolean
51 import org.mockito.Mockito.doReturn
52 
cellNcnull53 private fun cellNc() = NetworkCapabilities.Builder()
54         .addTransportType(TRANSPORT_CELLULAR)
55         .addCapability(NET_CAPABILITY_INTERNET)
56         .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
57         .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
58         .build()
59 private fun cellRequest() = NetworkRequest.Builder()
60         .addTransportType(TRANSPORT_CELLULAR)
61         .build()
62 private fun wifiNc() = NetworkCapabilities.Builder()
63         .addTransportType(TRANSPORT_WIFI)
64         .addCapability(NET_CAPABILITY_INTERNET)
65         .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
66         .addCapability(NET_CAPABILITY_NOT_METERED)
67         .build()
68 private fun wifiRequest() = NetworkRequest.Builder()
69         .addTransportType(TRANSPORT_WIFI)
70         .build()
71 
72 @RunWith(DevSdkIgnoreRunner::class)
73 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
74 class CSBlockedReasonsTest : CSTest() {
75 
76     inner class DetailedBlockedStatusCallback : TestableNetworkCallback() {
77         override fun onBlockedStatusChanged(network: Network, blockedReasons: Int) {
78             history.add(BlockedStatusInt(network, blockedReasons))
79         }
80 
81         fun expectBlockedStatusChanged(network: Network, blockedReasons: Int) {
82             expect<BlockedStatusInt>(network) { it.reason == blockedReasons }
83         }
84     }
85 
86     @Test
87     fun testBlockedReasons_onAvailable() {
88         doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER)
89                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
90 
91         val cellAgent = Agent(nc = cellNc())
92         cellAgent.connect()
93         val wifiAgent = Agent(nc = wifiNc())
94         wifiAgent.connect()
95 
96         val cellCb = DetailedBlockedStatusCallback()
97         val wifiCb = DetailedBlockedStatusCallback()
98         cm.requestNetwork(cellRequest(), cellCb)
99         cm.requestNetwork(wifiRequest(), wifiCb)
100 
101         cellCb.expectAvailableCallbacks(
102                 cellAgent.network,
103                 validated = false,
104                 blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER
105         )
106         wifiCb.expectAvailableCallbacks(
107                 wifiAgent.network,
108                 validated = false,
109                 blockedReason = BLOCKED_REASON_DOZE
110         )
111 
112         cellAgent.disconnect()
113         wifiAgent.disconnect()
114         cm.unregisterNetworkCallback(cellCb)
115         cm.unregisterNetworkCallback(wifiCb)
116     }
117 
118     @Test
119     fun testBlockedReasons_dataSaverChanged() {
120         doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
121                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
122         doReturn(true).`when`(netd).bandwidthEnableDataSaver(anyBoolean())
123 
124         val cellCb = DetailedBlockedStatusCallback()
125         val wifiCb = DetailedBlockedStatusCallback()
126         cm.requestNetwork(cellRequest(), cellCb)
127         cm.requestNetwork(wifiRequest(), wifiCb)
128 
129         val cellAgent = Agent(nc = cellNc())
130         cellAgent.connect()
131         val wifiAgent = Agent(nc = wifiNc())
132         wifiAgent.connect()
133         cellCb.expectAvailableCallbacks(
134                 cellAgent.network,
135                 validated = false,
136                 blockedReason = BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
137         )
138         wifiCb.expectAvailableCallbacks(
139                 wifiAgent.network,
140                 validated = false,
141                 blockedReason = BLOCKED_REASON_APP_BACKGROUND
142         )
143 
144         // Disable data saver
145         doReturn(BLOCKED_REASON_APP_BACKGROUND)
146                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
147         cm.setDataSaverEnabled(false)
148         cellCb.expectBlockedStatusChanged(cellAgent.network, BLOCKED_REASON_APP_BACKGROUND)
149 
150         // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
151         // bpfNetMaps.getNetPermForUid throws exception.
152         // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
153         // method on this callback was called, but it does not guarantee that ConnectivityService
154         // has finished processing all onBlockedStatusChanged callbacks for all requests.
155         waitForIdle()
156         // Enable data saver
157         doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
158                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
159         cm.setDataSaverEnabled(true)
160         cellCb.expectBlockedStatusChanged(
161                 cellAgent.network,
162                 BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
163         )
164         // BlockedStatus does not change for the non-metered network
165         wifiCb.assertNoCallback()
166 
167         cellAgent.disconnect()
168         wifiAgent.disconnect()
169         cm.unregisterNetworkCallback(cellCb)
170         cm.unregisterNetworkCallback(wifiCb)
171     }
172 
173     @Test
174     fun testBlockedReasons_setUidFirewallRule() {
175         doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
176                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
177 
178         val cellCb = DetailedBlockedStatusCallback()
179         val wifiCb = DetailedBlockedStatusCallback()
180         cm.requestNetwork(cellRequest(), cellCb)
181         cm.requestNetwork(wifiRequest(), wifiCb)
182 
183         val cellAgent = Agent(nc = cellNc())
184         cellAgent.connect()
185         val wifiAgent = Agent(nc = wifiNc())
186         wifiAgent.connect()
187         cellCb.expectAvailableCallbacks(
188                 cellAgent.network,
189                 validated = false,
190                 blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
191         )
192         wifiCb.expectAvailableCallbacks(
193                 wifiAgent.network,
194                 validated = false,
195                 blockedReason = BLOCKED_REASON_DOZE
196         )
197 
198         // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
199         // bpfNetMaps.getNetPermForUid throws exception.
200         // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
201         // method on this callback was called, but it does not guarantee that ConnectivityService
202         // has finished processing all onBlockedStatusChanged callbacks for all requests.
203         waitForIdle()
204         // Set RULE_ALLOW on metered deny chain
205         doReturn(BLOCKED_REASON_DOZE)
206                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
207         cm.setUidFirewallRule(
208                 FIREWALL_CHAIN_METERED_DENY_USER,
209                 Process.myUid(),
210                 FIREWALL_RULE_ALLOW
211         )
212         cellCb.expectBlockedStatusChanged(
213                 cellAgent.network,
214                 BLOCKED_REASON_DOZE
215         )
216         // BlockedStatus does not change for the non-metered network
217         wifiCb.assertNoCallback()
218 
219         // Set RULE_DENY on metered deny chain
220         doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
221                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
222         cm.setUidFirewallRule(
223                 FIREWALL_CHAIN_METERED_DENY_USER,
224                 Process.myUid(),
225                 FIREWALL_RULE_DENY
226         )
227         cellCb.expectBlockedStatusChanged(
228                 cellAgent.network,
229                 BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
230         )
231         // BlockedStatus does not change for the non-metered network
232         wifiCb.assertNoCallback()
233 
234         cellAgent.disconnect()
235         wifiAgent.disconnect()
236         cm.unregisterNetworkCallback(cellCb)
237         cm.unregisterNetworkCallback(wifiCb)
238     }
239 
240     @Test
241     fun testBlockedReasons_setFirewallChainEnabled() {
242         doReturn(BLOCKED_REASON_NONE)
243                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
244 
245         val wifiCb = DetailedBlockedStatusCallback()
246         cm.requestNetwork(wifiRequest(), wifiCb)
247         val wifiAgent = Agent(nc = wifiNc())
248         wifiAgent.connect()
249         wifiCb.expectAvailableCallbacks(
250                 wifiAgent.network,
251                 validated = false,
252                 blockedReason = BLOCKED_REASON_NONE
253         )
254 
255         // Enable dozable firewall chain
256         doReturn(BLOCKED_REASON_DOZE)
257                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
258         cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true)
259         wifiCb.expectBlockedStatusChanged(
260                 wifiAgent.network,
261                 BLOCKED_REASON_DOZE
262         )
263 
264         // Disable dozable firewall chain
265         doReturn(BLOCKED_REASON_NONE)
266                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
267         cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, false)
268         wifiCb.expectBlockedStatusChanged(
269                 wifiAgent.network,
270                 BLOCKED_REASON_NONE
271         )
272 
273         wifiAgent.disconnect()
274         cm.unregisterNetworkCallback(wifiCb)
275     }
276 
277     @Test
278     fun testBlockedReasons_replaceFirewallChain() {
279         doReturn(BLOCKED_REASON_APP_BACKGROUND)
280                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
281 
282         val wifiCb = DetailedBlockedStatusCallback()
283         cm.requestNetwork(wifiRequest(), wifiCb)
284         val wifiAgent = Agent(nc = wifiNc())
285         wifiAgent.connect()
286         wifiCb.expectAvailableCallbacks(
287                 wifiAgent.network,
288                 validated = false,
289                 blockedReason = BLOCKED_REASON_APP_BACKGROUND
290         )
291 
292         // Put uid on background firewall chain
293         doReturn(BLOCKED_REASON_NONE)
294                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
295         cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(Process.myUid()))
296         wifiCb.expectBlockedStatusChanged(
297                 wifiAgent.network,
298                 BLOCKED_REASON_NONE
299         )
300 
301         // Remove uid from background firewall chain
302         doReturn(BLOCKED_REASON_APP_BACKGROUND)
303                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
304         cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf())
305         wifiCb.expectBlockedStatusChanged(
306                 wifiAgent.network,
307                 BLOCKED_REASON_APP_BACKGROUND
308         )
309 
310         wifiAgent.disconnect()
311         cm.unregisterNetworkCallback(wifiCb)
312     }
313 
314     @Test
315     fun testBlockedReasons_perAppDefaultNetwork() {
316         doReturn(BLOCKED_METERED_REASON_USER_RESTRICTED)
317                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
318 
319         val cellCb = DetailedBlockedStatusCallback()
320         val wifiCb = DetailedBlockedStatusCallback()
321         cm.requestNetwork(cellRequest(), cellCb)
322         cm.requestNetwork(wifiRequest(), wifiCb)
323 
324         val cellAgent = Agent(nc = cellNc())
325         cellAgent.connect()
326         val wifiAgent = Agent(nc = wifiNc())
327         wifiAgent.connect()
328 
329         val cb = DetailedBlockedStatusCallback()
330         cm.registerDefaultNetworkCallback(cb)
331         cb.expectAvailableCallbacks(
332                 wifiAgent.network,
333                 validated = false,
334                 blockedReason = BLOCKED_REASON_NONE
335         )
336 
337         // CS must send correct blocked reasons after per app default network change
338         ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf(Process.myUid()))
339         service.updateMobileDataPreferredUids()
340         cb.expectAvailableCallbacks(
341                 cellAgent.network,
342                 validated = false,
343                 blockedReason = BLOCKED_METERED_REASON_USER_RESTRICTED
344         )
345 
346         // Remove per app default network request
347         ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf())
348         service.updateMobileDataPreferredUids()
349         cb.expectAvailableCallbacks(
350                 wifiAgent.network,
351                 validated = false,
352                 blockedReason = BLOCKED_REASON_NONE
353         )
354 
355         cellAgent.disconnect()
356         wifiAgent.disconnect()
357         cm.unregisterNetworkCallback(cellCb)
358         cm.unregisterNetworkCallback(wifiCb)
359         cm.unregisterNetworkCallback(cb)
360     }
361 
362     private fun doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission: Boolean) {
363         doReturn(PERMISSION_NONE).`when`(bpfNetMaps).getNetPermForUid(Process.myUid())
364 
365         val wifiCb = DetailedBlockedStatusCallback()
366         cm.requestNetwork(wifiRequest(), wifiCb)
367         val wifiAgent = Agent(nc = wifiNc())
368         wifiAgent.connect()
369         val expectedBlockedReason = if (blockedByNoInternetPermission) {
370             BLOCKED_REASON_NETWORK_RESTRICTED
371         } else {
372             BLOCKED_REASON_NONE
373         }
374         wifiCb.expectAvailableCallbacks(
375                 wifiAgent.network,
376                 validated = false,
377                 blockedReason = expectedBlockedReason
378         )
379 
380         // Enable background firewall chain
381         doReturn(BLOCKED_REASON_APP_BACKGROUND)
382                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
383         cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
384         if (blockedByNoInternetPermission) {
385             wifiCb.expectBlockedStatusChanged(
386                     wifiAgent.network,
387                     BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
388             )
389         }
390         // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
391         // bpfNetMaps.getNetPermForUid throws exception.
392         // ConnectivityService might haven't finished checking blocked status for all requests.
393         waitForIdle()
394 
395         // Disable background firewall chain
396         doReturn(BLOCKED_REASON_NONE)
397                 .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
398         cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
399         if (blockedByNoInternetPermission) {
400             wifiCb.expectBlockedStatusChanged(
401                     wifiAgent.network,
402                     BLOCKED_REASON_NETWORK_RESTRICTED
403             )
404         } else {
405             // No callback is expected since blocked reasons does not change from
406             // BLOCKED_REASON_NONE.
407             wifiCb.assertNoCallback()
408         }
409     }
410 
411     @Test
412     fun testBlockedReasonsNoInternetPermission_changeDisabled() {
413         deps.setChangeIdEnabled(false, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
414         doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = false)
415     }
416 
417     @Test
418     fun testBlockedReasonsNoInternetPermission_changeEnabled() {
419         deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
420         doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
421     }
422 }
423