| /* |
| * Copyright (C) 2024 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 com.android.server |
| |
| import android.app.ActivityManager.UidFrozenStateChangedCallback |
| import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN |
| import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN |
| import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND |
| import android.net.ConnectivityManager.BLOCKED_REASON_NONE |
| import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND |
| import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW |
| import android.net.ConnectivityManager.FIREWALL_RULE_DENY |
| import android.net.LinkProperties |
| import android.net.NetworkCapabilities |
| import android.os.Build |
| import com.android.net.module.util.BaseNetdUnsolicitedEventListener |
| import com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS |
| import com.android.testutils.DevSdkIgnoreRule |
| import com.android.testutils.DevSdkIgnoreRunner |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.mockito.ArgumentCaptor |
| import org.mockito.Mockito.any |
| import org.mockito.Mockito.doReturn |
| import org.mockito.Mockito.inOrder |
| import org.mockito.Mockito.never |
| import org.mockito.Mockito.verify |
| |
| private const val TIMESTAMP = 1234L |
| private const val TEST_UID = 1234 |
| private const val TEST_UID2 = 5678 |
| private const val TEST_CELL_IFACE = "test_rmnet" |
| |
| private fun cellNc() = NetworkCapabilities.Builder() |
| .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) |
| .build() |
| |
| private fun cellLp() = LinkProperties().also{ |
| it.interfaceName = TEST_CELL_IFACE |
| } |
| |
| @RunWith(DevSdkIgnoreRunner::class) |
| @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| class CSDestroySocketTest : CSTest() { |
| private fun getRegisteredNetdUnsolicitedEventListener(): BaseNetdUnsolicitedEventListener { |
| val captor = ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener::class.java) |
| verify(netd).registerUnsolicitedEventListener(captor.capture()) |
| return captor.value |
| } |
| |
| private fun getUidFrozenStateChangedCallback(): UidFrozenStateChangedCallback { |
| val captor = ArgumentCaptor.forClass(UidFrozenStateChangedCallback::class.java) |
| verify(activityManager).registerUidFrozenStateChangedCallback(any(), captor.capture()) |
| return captor.value |
| } |
| |
| private fun doTestBackgroundRestrictionDestroySockets( |
| restrictionWithIdleNetwork: Boolean, |
| expectDelay: Boolean |
| ) { |
| val netdEventListener = getRegisteredNetdUnsolicitedEventListener() |
| val inOrder = inOrder(destroySocketsWrapper) |
| |
| val cellAgent = Agent(nc = cellNc(), lp = cellLp()) |
| cellAgent.connect() |
| if (restrictionWithIdleNetwork) { |
| // Make cell default network idle |
| netdEventListener.onInterfaceClassActivityChanged( |
| false, // isActive |
| cellAgent.network.netId, |
| TIMESTAMP, |
| TEST_UID |
| ) |
| } |
| |
| // Set deny rule on background chain for TEST_UID |
| doReturn(BLOCKED_REASON_APP_BACKGROUND) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID) |
| cm.setUidFirewallRule( |
| FIREWALL_CHAIN_BACKGROUND, |
| TEST_UID, |
| FIREWALL_RULE_DENY |
| ) |
| waitForIdle() |
| if (expectDelay) { |
| inOrder.verify(destroySocketsWrapper, never()) |
| .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| } else { |
| inOrder.verify(destroySocketsWrapper) |
| .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| } |
| |
| netdEventListener.onInterfaceClassActivityChanged( |
| true, // isActive |
| cellAgent.network.netId, |
| TIMESTAMP, |
| TEST_UID |
| ) |
| waitForIdle() |
| if (expectDelay) { |
| inOrder.verify(destroySocketsWrapper) |
| .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| } else { |
| inOrder.verify(destroySocketsWrapper, never()) |
| .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| } |
| |
| cellAgent.disconnect() |
| } |
| |
| @Test |
| @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)]) |
| fun testBackgroundAppDestroySockets() { |
| doTestBackgroundRestrictionDestroySockets( |
| restrictionWithIdleNetwork = true, |
| expectDelay = true |
| ) |
| } |
| |
| @Test |
| @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)]) |
| fun testBackgroundAppDestroySockets_activeNetwork() { |
| doTestBackgroundRestrictionDestroySockets( |
| restrictionWithIdleNetwork = false, |
| expectDelay = false |
| ) |
| } |
| |
| @Test |
| @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, false)]) |
| fun testBackgroundAppDestroySockets_featureIsDisabled() { |
| doTestBackgroundRestrictionDestroySockets( |
| restrictionWithIdleNetwork = true, |
| expectDelay = false |
| ) |
| } |
| |
| @Test |
| fun testReplaceFirewallChain() { |
| val netdEventListener = getRegisteredNetdUnsolicitedEventListener() |
| val inOrder = inOrder(destroySocketsWrapper) |
| |
| val cellAgent = Agent(nc = cellNc(), lp = cellLp()) |
| cellAgent.connect() |
| // Make cell default network idle |
| netdEventListener.onInterfaceClassActivityChanged( |
| false, // isActive |
| cellAgent.network.netId, |
| TIMESTAMP, |
| TEST_UID |
| ) |
| |
| // Set allow rule on background chain for TEST_UID |
| doReturn(BLOCKED_REASON_NONE) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID) |
| cm.setUidFirewallRule( |
| FIREWALL_CHAIN_BACKGROUND, |
| TEST_UID, |
| FIREWALL_RULE_ALLOW |
| ) |
| // Set deny rule on background chain for TEST_UID |
| doReturn(BLOCKED_REASON_APP_BACKGROUND) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2) |
| cm.setUidFirewallRule( |
| FIREWALL_CHAIN_BACKGROUND, |
| TEST_UID2, |
| FIREWALL_RULE_DENY |
| ) |
| |
| // Put only TEST_UID2 on background chain (deny TEST_UID and allow TEST_UID2) |
| doReturn(setOf(TEST_UID)) |
| .`when`(bpfNetMaps).getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_BACKGROUND) |
| doReturn(BLOCKED_REASON_APP_BACKGROUND) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID) |
| doReturn(BLOCKED_REASON_NONE) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2) |
| cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(TEST_UID2)) |
| waitForIdle() |
| inOrder.verify(destroySocketsWrapper, never()) |
| .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| |
| netdEventListener.onInterfaceClassActivityChanged( |
| true, // isActive |
| cellAgent.network.netId, |
| TIMESTAMP, |
| TEST_UID |
| ) |
| waitForIdle() |
| inOrder.verify(destroySocketsWrapper) |
| .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| |
| cellAgent.disconnect() |
| } |
| |
| private fun doTestDestroySockets( |
| isFrozen: Boolean, |
| denyOnBackgroundChain: Boolean, |
| enableBackgroundChain: Boolean, |
| expectDestroySockets: Boolean |
| ) { |
| val netdEventListener = getRegisteredNetdUnsolicitedEventListener() |
| val frozenStateCallback = getUidFrozenStateChangedCallback() |
| |
| // Make cell default network idle |
| val cellAgent = Agent(nc = cellNc(), lp = cellLp()) |
| cellAgent.connect() |
| netdEventListener.onInterfaceClassActivityChanged( |
| false, // isActive |
| cellAgent.network.netId, |
| TIMESTAMP, |
| TEST_UID |
| ) |
| |
| // Set deny rule on background chain for TEST_UID |
| doReturn(BLOCKED_REASON_APP_BACKGROUND) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID) |
| cm.setUidFirewallRule( |
| FIREWALL_CHAIN_BACKGROUND, |
| TEST_UID, |
| FIREWALL_RULE_DENY |
| ) |
| |
| // Freeze TEST_UID |
| frozenStateCallback.onUidFrozenStateChanged( |
| intArrayOf(TEST_UID), |
| intArrayOf(UID_FROZEN_STATE_FROZEN) |
| ) |
| |
| if (!isFrozen) { |
| // Unfreeze TEST_UID |
| frozenStateCallback.onUidFrozenStateChanged( |
| intArrayOf(TEST_UID), |
| intArrayOf(UID_FROZEN_STATE_UNFROZEN) |
| ) |
| } |
| if (!enableBackgroundChain) { |
| // Disable background chain |
| cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false) |
| } |
| if (!denyOnBackgroundChain) { |
| // Set allow rule on background chain for TEST_UID |
| doReturn(BLOCKED_REASON_NONE) |
| .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID) |
| cm.setUidFirewallRule( |
| FIREWALL_CHAIN_BACKGROUND, |
| TEST_UID, |
| FIREWALL_RULE_ALLOW |
| ) |
| } |
| verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| |
| // Make cell network active |
| netdEventListener.onInterfaceClassActivityChanged( |
| true, // isActive |
| cellAgent.network.netId, |
| TIMESTAMP, |
| TEST_UID |
| ) |
| waitForIdle() |
| |
| if (expectDestroySockets) { |
| verify(destroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| } else { |
| verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) |
| } |
| } |
| |
| @Test |
| fun testDestroySockets_backgroundDeny_frozen() { |
| doTestDestroySockets( |
| isFrozen = true, |
| denyOnBackgroundChain = true, |
| enableBackgroundChain = true, |
| expectDestroySockets = true |
| ) |
| } |
| |
| @Test |
| fun testDestroySockets_backgroundDeny_nonFrozen() { |
| doTestDestroySockets( |
| isFrozen = false, |
| denyOnBackgroundChain = true, |
| enableBackgroundChain = true, |
| expectDestroySockets = true |
| ) |
| } |
| |
| @Test |
| fun testDestroySockets_backgroundAllow_frozen() { |
| doTestDestroySockets( |
| isFrozen = true, |
| denyOnBackgroundChain = false, |
| enableBackgroundChain = true, |
| expectDestroySockets = true |
| ) |
| } |
| |
| @Test |
| fun testDestroySockets_backgroundAllow_nonFrozen() { |
| // If the app is neither frozen nor under background restriction, sockets are not |
| // destroyed |
| doTestDestroySockets( |
| isFrozen = false, |
| denyOnBackgroundChain = false, |
| enableBackgroundChain = true, |
| expectDestroySockets = false |
| ) |
| } |
| |
| @Test |
| fun testDestroySockets_backgroundChainDisabled_nonFrozen() { |
| // If the app is neither frozen nor under background restriction, sockets are not |
| // destroyed |
| doTestDestroySockets( |
| isFrozen = false, |
| denyOnBackgroundChain = true, |
| enableBackgroundChain = false, |
| expectDestroySockets = false |
| ) |
| } |
| } |