Track multiple network activities on V+

on V+ devices, NetworkActivityTracker adds idleTimer when the network is
first connected and removes idleTimer when the network is disconnected
so that activity tracker can receive netd
onInterfaceClassActivityChanged callback for multiple networks.

Bug: 267870186
Bug: 279380356
Test: atest FrameworksNetTests
Change-Id: I6f3a95c27e3e58f3f60c40065f562d00431a56b1
This commit is contained in:
Motomu Utsumi
2023-05-29 16:07:19 +09:00
parent 5fce43e548
commit 1f5ffa4273
4 changed files with 334 additions and 32 deletions

View File

@@ -11426,12 +11426,13 @@ public class ConnectivityServiceTest {
}
@Test
public void testOnNetworkActive_NewEthernetConnects_CallbackNotCalled() throws Exception {
// LegacyNetworkActivityTracker calls onNetworkActive callback only for networks that
// tracker adds the idle timer to. And the tracker does not set the idle timer for the
// ethernet network.
public void testOnNetworkActive_NewEthernetConnects_Callback() throws Exception {
// On pre-V devices, LegacyNetworkActivityTracker calls onNetworkActive callback only for
// networks that tracker adds the idle timer to. And the tracker does not set the idle timer
// for the ethernet network.
// So onNetworkActive is not called when the ethernet becomes the default network
doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, false /* expectCallback */);
final boolean expectCallback = mDeps.isAtLeastV();
doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, expectCallback);
}
@Test
@@ -11485,8 +11486,17 @@ public class ConnectivityServiceTest {
networkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
eq(wifiIdleTimerLabel));
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(cellIdleTimerLabel));
if (mDeps.isAtLeastV()) {
// V+ devices add idleTimer when the network is first connected and remove when the
// network is disconnected.
verify(mMockNetd, never()).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(mCellAgent.getNetwork().netId)));
} else {
// pre V devices add idleTimer when the network becomes the default network and remove
// when the network becomes no longer the default network.
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
}
// Disconnect wifi and switch back to cell
reset(mMockNetd);
@@ -11495,8 +11505,13 @@ public class ConnectivityServiceTest {
assertNoCallbacks(networkCallback);
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
eq(wifiIdleTimerLabel));
verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
eq(cellIdleTimerLabel));
if (mDeps.isAtLeastV()) {
verify(mMockNetd, never()).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(mCellAgent.getNetwork().netId)));
} else {
verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
}
// reconnect wifi
reset(mMockNetd);
@@ -11511,19 +11526,29 @@ public class ConnectivityServiceTest {
networkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
eq(wifiIdleTimerLabel));
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(cellIdleTimerLabel));
if (mDeps.isAtLeastV()) {
verify(mMockNetd, never()).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(mCellAgent.getNetwork().netId)));
} else {
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
}
// Disconnect cell
reset(mMockNetd);
mCellAgent.disconnect();
networkCallback.expect(LOST, mCellAgent);
// LOST callback is triggered earlier than removing idle timer. Broadcast should also be
// sent as network being switched. Ensure rule removal for cell will not be triggered
// unexpectedly before network being removed.
waitForIdle();
verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(cellIdleTimerLabel));
if (mDeps.isAtLeastV()) {
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(mCellAgent.getNetwork().netId)));
} else {
// LOST callback is triggered earlier than removing idle timer. Broadcast should also be
// sent as network being switched. Ensure rule removal for cell will not be triggered
// unexpectedly before network being removed.
verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
}
verify(mMockNetd, times(1)).networkDestroy(eq(mCellAgent.getNetwork().netId));
verify(mMockDnsResolver, times(1)).destroyNetworkCache(eq(mCellAgent.getNetwork().netId));
@@ -11538,6 +11563,21 @@ public class ConnectivityServiceTest {
mCm.unregisterNetworkCallback(networkCallback);
}
@Test
public void testDataActivityTracking_VpnNetwork() throws Exception {
mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiAgent.connect(true /* validated */);
mMockVpn.setUnderlyingNetworks(new Network[] { mWiFiAgent.getNetwork() });
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(VPN_IFNAME);
mMockVpn.establishForMyUid(lp);
// NetworkActivityTracker should not track the VPN network since VPN can change the
// underlying network without disconnect.
verify(mMockNetd, never()).idletimerAddInterface(eq(VPN_IFNAME), anyInt(), any());
}
private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception {
String[] values = tcpBufferSizes.split(",");
String rmemValues = String.join(" ", values[0], values[1], values[2]);

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2023 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.net.ConnectivityManager
import android.net.ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE
import android.net.ConnectivityManager.EXTRA_DEVICE_TYPE
import android.net.ConnectivityManager.EXTRA_IS_ACTIVE
import android.net.ConnectivityManager.EXTRA_REALTIME_NS
import android.net.LinkProperties
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_IMS
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
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.os.Build
import android.os.ConditionVariable
import androidx.test.filters.SmallTest
import com.android.net.module.util.BaseNetdUnsolicitedEventListener
import com.android.server.CSTest.CSContext
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import kotlin.test.assertNotNull
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
private const val DATA_CELL_IFNAME = "rmnet_data"
private const val IMS_CELL_IFNAME = "rmnet_ims"
private const val WIFI_IFNAME = "wlan0"
private const val TIMESTAMP = 1234L
private const val NETWORK_ACTIVITY_NO_UID = -1
private const val PACKAGE_UID = 123
private const val CALLBACK_TIMEOUT_MS = 250L
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class CSNetworkActivityTest : CSTest() {
private fun getRegisteredNetdUnsolicitedEventListener(): BaseNetdUnsolicitedEventListener {
val captor = ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener::class.java)
verify(netd).registerUnsolicitedEventListener(captor.capture())
return captor.value
}
@Test
fun testInterfaceClassActivityChanged_NonDefaultNetwork() {
val netdUnsolicitedEventListener = getRegisteredNetdUnsolicitedEventListener()
val cellNr = NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.build()
val cellCb = TestableNetworkCallback()
// Request cell network to keep cell network up
cm.requestNetwork(cellNr, cellCb)
val defaultCb = TestableNetworkCallback()
cm.registerDefaultNetworkCallback(defaultCb)
val cellNc = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.build()
val cellLp = LinkProperties().apply {
interfaceName = DATA_CELL_IFNAME
}
// Connect Cellular network
val cellAgent = Agent(nc = cellNc, lp = cellLp)
cellAgent.connect()
defaultCb.expectAvailableCallbacks(cellAgent.network, validated = false)
val wifiNc = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.build()
val wifiLp = LinkProperties().apply {
interfaceName = WIFI_IFNAME
}
// Connect Wi-Fi network, Wi-Fi network should be the default network.
val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
wifiAgent.connect()
defaultCb.expectAvailableCallbacks(wifiAgent.network, validated = false)
val onNetworkActiveCv = ConditionVariable()
val listener = ConnectivityManager.OnNetworkActiveListener { onNetworkActiveCv::open }
cm.addDefaultNetworkActiveListener(listener)
// Cellular network (non default network) goes to inactive state.
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
cellAgent.network.netId, TIMESTAMP, NETWORK_ACTIVITY_NO_UID)
// Non-default network activity change does not change default network activity
assertFalse(onNetworkActiveCv.block(CALLBACK_TIMEOUT_MS))
context.expectNoDataActivityBroadcast(0 /* timeoutMs */)
assertTrue(cm.isDefaultNetworkActive)
// Cellular network (non default network) goes to active state.
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
cellAgent.network.netId, TIMESTAMP, PACKAGE_UID)
// Non-default network activity change does not change default network activity
assertFalse(onNetworkActiveCv.block(CALLBACK_TIMEOUT_MS))
context.expectNoDataActivityBroadcast(0 /* timeoutMs */)
assertTrue(cm.isDefaultNetworkActive)
cm.unregisterNetworkCallback(cellCb)
cm.unregisterNetworkCallback(defaultCb)
cm.removeDefaultNetworkActiveListener(listener)
}
@Test
fun testDataActivityTracking_MultiCellNetwork() {
val dataNetworkNc = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.build()
val dataNetworkNr = NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.build()
val dataNetworkLp = LinkProperties().apply {
interfaceName = DATA_CELL_IFNAME
}
val dataNetworkCb = TestableNetworkCallback()
cm.requestNetwork(dataNetworkNr, dataNetworkCb)
val dataNetworkAgent = Agent(nc = dataNetworkNc, lp = dataNetworkLp)
val dataNetworkNetId = dataNetworkAgent.network.netId.toString()
val imsNetworkNc = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_IMS)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.build()
val imsNetworkNr = NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_IMS)
.build()
val imsNetworkLp = LinkProperties().apply {
interfaceName = IMS_CELL_IFNAME
}
val imsNetworkCb = TestableNetworkCallback()
cm.requestNetwork(imsNetworkNr, imsNetworkCb)
val imsNetworkAgent = Agent(nc = imsNetworkNc, lp = imsNetworkLp)
val imsNetworkNetId = imsNetworkAgent.network.netId.toString()
dataNetworkAgent.connect()
dataNetworkCb.expectAvailableCallbacks(dataNetworkAgent.network, validated = false)
imsNetworkAgent.connect()
imsNetworkCb.expectAvailableCallbacks(imsNetworkAgent.network, validated = false)
// Both cell networks have idleTimers
verify(netd).idletimerAddInterface(eq(DATA_CELL_IFNAME), anyInt(), eq(dataNetworkNetId))
verify(netd).idletimerAddInterface(eq(IMS_CELL_IFNAME), anyInt(), eq(imsNetworkNetId))
verify(netd, never()).idletimerRemoveInterface(eq(DATA_CELL_IFNAME), anyInt(),
eq(dataNetworkNetId))
verify(netd, never()).idletimerRemoveInterface(eq(IMS_CELL_IFNAME), anyInt(),
eq(imsNetworkNetId))
dataNetworkAgent.disconnect()
dataNetworkCb.expect<Lost>(dataNetworkAgent.network)
verify(netd).idletimerRemoveInterface(eq(DATA_CELL_IFNAME), anyInt(), eq(dataNetworkNetId))
imsNetworkAgent.disconnect()
imsNetworkCb.expect<Lost>(imsNetworkAgent.network)
verify(netd).idletimerRemoveInterface(eq(IMS_CELL_IFNAME), anyInt(), eq(imsNetworkNetId))
cm.unregisterNetworkCallback(dataNetworkCb)
cm.unregisterNetworkCallback(imsNetworkCb)
}
}
internal fun CSContext.expectDataActivityBroadcast(
deviceType: Int,
isActive: Boolean,
tsNanos: Long
) {
assertNotNull(orderedBroadcastAsUserHistory.poll(BROADCAST_TIMEOUT_MS) {
intent -> intent.action.equals(ACTION_DATA_ACTIVITY_CHANGE) &&
intent.getIntExtra(EXTRA_DEVICE_TYPE, -1) == deviceType &&
intent.getBooleanExtra(EXTRA_IS_ACTIVE, !isActive) == isActive &&
intent.getLongExtra(EXTRA_REALTIME_NS, -1) == tsNanos
})
}

View File

@@ -43,6 +43,7 @@ import android.net.NetworkScore
import android.net.PacProxyManager
import android.net.networkstack.NetworkStackClientBase
import android.os.BatteryStatsManager
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.os.UserHandle
@@ -54,6 +55,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.app.IBatteryStats
import com.android.internal.util.test.BroadcastInterceptingContext
import com.android.modules.utils.build.SdkLevel
import com.android.net.module.util.ArrayTrackRecord
import com.android.networkstack.apishim.common.UnsupportedApiLevelException
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
@@ -65,6 +67,7 @@ import com.android.server.connectivity.ProxyTracker
import com.android.testutils.visibleOnHandlerThread
import com.android.testutils.waitForIdle
import java.util.concurrent.Executors
import kotlin.test.assertNull
import kotlin.test.fail
import org.mockito.AdditionalAnswers.delegatesTo
import org.mockito.Mockito.doAnswer
@@ -72,6 +75,7 @@ import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
internal const val HANDLER_TIMEOUT_MS = 2_000
internal const val BROADCAST_TIMEOUT_MS = 3_000L
internal const val TEST_PACKAGE_NAME = "com.android.test.package"
internal const val WIFI_WOL_IFNAME = "test_wlan_wol"
internal val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
@@ -282,6 +286,26 @@ open class CSTest {
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
else -> super.getSystemService(serviceName)
}
internal val orderedBroadcastAsUserHistory = ArrayTrackRecord<Intent>().newReadHead()
fun expectNoDataActivityBroadcast(timeoutMs: Int) {
assertNull(orderedBroadcastAsUserHistory.poll(
timeoutMs.toLong()) { intent -> true })
}
override fun sendOrderedBroadcastAsUser(
intent: Intent,
user: UserHandle,
receiverPermission: String?,
resultReceiver: BroadcastReceiver?,
scheduler: Handler?,
initialCode: Int,
initialData: String?,
initialExtras: Bundle?
) {
orderedBroadcastAsUserHistory.add(intent)
}
}
// Utility methods for subclasses to use