Add blocked reason for internet permission
Currently, even if apps don't have INTERNET permission and can not use network, network access is considered not blocked and ConnectivityService send onBlockedStatusChanged callback with blocked=false. This CL introduced BLOCKED_REASON_NETWORK_RESTRICTED and consider network access from apps targetting W+ on V+ releases is blocked if apps don't have INTERNET permission. Permission is set when the user is added or package is installed and permission is removed when the user is removed or packages is uninstalled. So ConnectivityService does not need to monitor permission change to send onBlockedStatusChanged callback by permission change. Test: CSBlockedReasonsTest Bug: 339559837 Change-Id: I58d2a4eddc714e205f5b96219f95b637f2826c58
This commit is contained in:
parent
d20727f3cc
commit
ee96ba0e60
4 changed files with 163 additions and 13 deletions
|
|
@ -53,6 +53,7 @@ import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
|
|||
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
|
||||
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
|
||||
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
|
||||
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
|
||||
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.EXTRA_DEVICE_TYPE;
|
||||
|
|
@ -9880,6 +9881,28 @@ public class ConnectivityServiceTest {
|
|||
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
|
||||
assertExtraInfoFromCmPresent(mCellAgent);
|
||||
|
||||
// Remove PERMISSION_INTERNET and disable NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
|
||||
doReturn(INetd.PERMISSION_NONE).when(mBpfNetMaps).getNetPermForUid(Process.myUid());
|
||||
mDeps.setChangeIdEnabled(false,
|
||||
NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, Process.myUid());
|
||||
|
||||
setBlockedReasonChanged(BLOCKED_REASON_DOZE);
|
||||
if (mDeps.isAtLeastV()) {
|
||||
// On V+, network access from app that does not have INTERNET permission is considered
|
||||
// not blocked if NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION is disabled.
|
||||
// So blocked status does not change from BLOCKED_REASON_NONE
|
||||
cellNetworkCallback.assertNoCallback();
|
||||
detailedCallback.assertNoCallback();
|
||||
} else {
|
||||
// On U-, onBlockedStatusChanged callback is called with blocked reasons CS receives
|
||||
// from NPMS callback regardless of permission app has.
|
||||
// Note that this cannot actually happen because on U-, NPMS will never notify any
|
||||
// blocked reasons for apps that don't have the INTERNET permission.
|
||||
cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
|
||||
detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
|
||||
cb -> cb.getReason() == BLOCKED_REASON_DOZE);
|
||||
}
|
||||
|
||||
mCm.unregisterNetworkCallback(cellNetworkCallback);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
|
|||
import android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED
|
||||
import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
|
||||
import android.net.ConnectivityManager.BLOCKED_REASON_DOZE
|
||||
import android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED
|
||||
import android.net.ConnectivityManager.BLOCKED_REASON_NONE
|
||||
import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
|
||||
import android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE
|
||||
|
|
@ -27,6 +28,7 @@ import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER
|
|||
import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
|
||||
import android.net.ConnectivityManager.FIREWALL_RULE_DENY
|
||||
import android.net.ConnectivitySettingsManager
|
||||
import android.net.INetd.PERMISSION_NONE
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||
|
|
@ -36,6 +38,7 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
|
|||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||
import android.net.NetworkRequest
|
||||
import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
|
|
@ -144,6 +147,12 @@ class CSBlockedReasonsTest : CSTest() {
|
|||
cm.setDataSaverEnabled(false)
|
||||
cellCb.expectBlockedStatusChanged(cellAgent.network, BLOCKED_REASON_APP_BACKGROUND)
|
||||
|
||||
// waitForIdle since stubbing bpfNetMaps while CS handler thread calls
|
||||
// bpfNetMaps.getNetPermForUid throws exception.
|
||||
// The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
|
||||
// method on this callback was called, but it does not guarantee that ConnectivityService
|
||||
// has finished processing all onBlockedStatusChanged callbacks for all requests.
|
||||
waitForIdle()
|
||||
// Enable data saver
|
||||
doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
|
||||
.`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
|
||||
|
|
@ -186,6 +195,12 @@ class CSBlockedReasonsTest : CSTest() {
|
|||
blockedReason = BLOCKED_REASON_DOZE
|
||||
)
|
||||
|
||||
// waitForIdle since stubbing bpfNetMaps while CS handler thread calls
|
||||
// bpfNetMaps.getNetPermForUid throws exception.
|
||||
// The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
|
||||
// method on this callback was called, but it does not guarantee that ConnectivityService
|
||||
// has finished processing all onBlockedStatusChanged callbacks for all requests.
|
||||
waitForIdle()
|
||||
// Set RULE_ALLOW on metered deny chain
|
||||
doReturn(BLOCKED_REASON_DOZE)
|
||||
.`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
|
||||
|
|
@ -343,4 +358,61 @@ class CSBlockedReasonsTest : CSTest() {
|
|||
cm.unregisterNetworkCallback(wifiCb)
|
||||
cm.unregisterNetworkCallback(cb)
|
||||
}
|
||||
|
||||
private fun doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission: Boolean) {
|
||||
doReturn(PERMISSION_NONE).`when`(bpfNetMaps).getNetPermForUid(Process.myUid())
|
||||
|
||||
val wifiCb = DetailedBlockedStatusCallback()
|
||||
cm.requestNetwork(wifiRequest(), wifiCb)
|
||||
val wifiAgent = Agent(nc = wifiNc())
|
||||
wifiAgent.connect()
|
||||
val expectedBlockedReason = if (blockedByNoInternetPermission) {
|
||||
BLOCKED_REASON_NETWORK_RESTRICTED
|
||||
} else {
|
||||
BLOCKED_REASON_NONE
|
||||
}
|
||||
wifiCb.expectAvailableCallbacks(
|
||||
wifiAgent.network,
|
||||
validated = false,
|
||||
blockedReason = expectedBlockedReason
|
||||
)
|
||||
|
||||
// Enable background firewall chain
|
||||
doReturn(BLOCKED_REASON_APP_BACKGROUND)
|
||||
.`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
|
||||
cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
|
||||
if (blockedByNoInternetPermission) {
|
||||
wifiCb.expectBlockedStatusChanged(
|
||||
wifiAgent.network,
|
||||
BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
|
||||
)
|
||||
}
|
||||
|
||||
// Disable background firewall chain
|
||||
doReturn(BLOCKED_REASON_NONE)
|
||||
.`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
|
||||
cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
|
||||
if (blockedByNoInternetPermission) {
|
||||
wifiCb.expectBlockedStatusChanged(
|
||||
wifiAgent.network,
|
||||
BLOCKED_REASON_NETWORK_RESTRICTED
|
||||
)
|
||||
} else {
|
||||
// No callback is expected since blocked reasons does not change from
|
||||
// BLOCKED_REASON_NONE.
|
||||
wifiCb.assertNoCallback()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBlockedReasonsNoInternetPermission_changeDisabled() {
|
||||
deps.setChangeIdEnabled(false, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
|
||||
doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBlockedReasonsNoInternetPermission_changeEnabled() {
|
||||
deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
|
||||
doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChange
|
|||
import com.android.testutils.TestableNetworkCallback
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.InOrder
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.timeout
|
||||
|
|
@ -96,6 +97,11 @@ class CSIngressDiscardRuleTests : CSTest() {
|
|||
private val LOCAL_IPV6_ADDRRESS = InetAddresses.parseNumericAddress("fe80::1234")
|
||||
private val LOCAL_IPV6_LINK_ADDRRESS = LinkAddress(LOCAL_IPV6_ADDRRESS, 64)
|
||||
|
||||
fun verifyNoMoreIngressDiscardRuleChange(inorder: InOrder) {
|
||||
inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
|
||||
inorder.verify(bpfNetMaps, never()).removeIngressDiscardRule(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVpnIngressDiscardRule_UpdateVpnAddress() {
|
||||
// non-VPN network whose address will be not duplicated with VPN address
|
||||
|
|
@ -148,7 +154,7 @@ class CSIngressDiscardRuleTests : CSTest() {
|
|||
|
||||
// IngressDiscardRule is added to the VPN address
|
||||
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
|
||||
inorder.verifyNoMoreInteractions()
|
||||
verifyNoMoreIngressDiscardRuleChange(inorder)
|
||||
|
||||
// The VPN interface name is changed
|
||||
val newlp = lp(VPN_IFNAME2, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
|
||||
|
|
@ -157,7 +163,7 @@ class CSIngressDiscardRuleTests : CSTest() {
|
|||
|
||||
// IngressDiscardRule is updated with the new interface name
|
||||
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME2)
|
||||
inorder.verifyNoMoreInteractions()
|
||||
verifyNoMoreIngressDiscardRuleChange(inorder)
|
||||
|
||||
agent.disconnect()
|
||||
inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
|
||||
|
|
@ -206,10 +212,10 @@ class CSIngressDiscardRuleTests : CSTest() {
|
|||
// IngressDiscardRule for IPV6_ADDRESS2 is removed but IngressDiscardRule for
|
||||
// IPV6_LINK_ADDRESS is not added since Wi-Fi also uses IPV6_LINK_ADDRESS
|
||||
inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS2)
|
||||
inorder.verifyNoMoreInteractions()
|
||||
verifyNoMoreIngressDiscardRuleChange(inorder)
|
||||
|
||||
vpnAgent.disconnect()
|
||||
inorder.verifyNoMoreInteractions()
|
||||
verifyNoMoreIngressDiscardRuleChange(inorder)
|
||||
|
||||
cm.unregisterNetworkCallback(cb)
|
||||
}
|
||||
|
|
@ -225,7 +231,7 @@ class CSIngressDiscardRuleTests : CSTest() {
|
|||
|
||||
// IngressDiscardRule is added to the VPN address
|
||||
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
|
||||
inorder.verifyNoMoreInteractions()
|
||||
verifyNoMoreIngressDiscardRuleChange(inorder)
|
||||
|
||||
val nr = nr(TRANSPORT_WIFI)
|
||||
val cb = TestableNetworkCallback()
|
||||
|
|
@ -247,7 +253,7 @@ class CSIngressDiscardRuleTests : CSTest() {
|
|||
// IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
|
||||
// with the Wi-Fi address
|
||||
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
|
||||
inorder.verifyNoMoreInteractions()
|
||||
verifyNoMoreIngressDiscardRuleChange(inorder)
|
||||
|
||||
// The Wi-Fi address is changed back to the same address as the VPN interface
|
||||
wifiAgent.sendLinkProperties(wifiLp)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import android.content.pm.UserInfo
|
|||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.INetd
|
||||
import android.net.INetd.PERMISSION_INTERNET
|
||||
import android.net.InetAddresses
|
||||
import android.net.LinkProperties
|
||||
import android.net.LocalNetworkConfig
|
||||
|
|
@ -89,6 +90,7 @@ import org.junit.Before
|
|||
import org.junit.Rule
|
||||
import org.junit.rules.TestName
|
||||
import org.mockito.AdditionalAnswers.delegatesTo
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.mock
|
||||
|
|
@ -187,7 +189,9 @@ open class CSTest {
|
|||
val connResources = makeMockConnResources(sysResources, packageManager)
|
||||
|
||||
val netd = mock<INetd>()
|
||||
val bpfNetMaps = mock<BpfNetMaps>()
|
||||
val bpfNetMaps = mock<BpfNetMaps>().also {
|
||||
doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
|
||||
}
|
||||
val clatCoordinator = mock<ClatCoordinator>()
|
||||
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
|
||||
val proxyTracker = ProxyTracker(
|
||||
|
|
|
|||
Loading…
Reference in a new issue