blob: bc5be7865ca067587c52b20e1c3c6889fb4dd104 [file] [log] [blame]
/*
* 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
)
}
}