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