blob: d861639ef012418bd06f96dbf918bdf2e7a41bc8 [file] [log] [blame]
/*
* Copyright (C) 2020 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 android.net.ip
import android.Manifest
import android.Manifest.permission.READ_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.net.IIpMemoryStore
import android.net.IIpMemoryStoreCallbacks
import android.net.NetworkStackIpMemoryStore
import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener
import android.net.ipmemorystore.NetworkAttributes
import android.net.ipmemorystore.Status
import android.net.networkstack.TestNetworkStackServiceClient
import android.os.Process
import android.provider.DeviceConfig
import android.util.ArrayMap
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.net.module.util.DeviceConfigUtils
import java.lang.System.currentTimeMillis
import java.net.Inet6Address
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
import org.junit.After
import org.junit.AfterClass
import org.junit.BeforeClass
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.anyString
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
// Stable AIDL method 5 in INetworkStackConnector is allowTestUid
private const val ALLOW_TEST_UID_INDEX = 5
/**
* Tests for IpClient, run with root access but no signature permissions.
*
* Tests run from this class interact with the real network stack process and can affect system
* state, e.g. by changing flags.
* State should be restored at the end of the test, but is not guaranteed if the test process is
* terminated during the run.
*/
class IpClientRootTest : IpClientIntegrationTestCommon() {
companion object {
private val TAG = IpClientRootTest::class.java.simpleName
private val automation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
private lateinit var nsClient: TestNetworkStackServiceClient
private lateinit var mStore: NetworkStackIpMemoryStore
private val mContext = InstrumentationRegistry.getInstrumentation().getContext()
private class IpMemoryStoreCallbacks(
private val fetchedFuture: CompletableFuture<IIpMemoryStore>
) : IIpMemoryStoreCallbacks.Stub() {
override fun onIpMemoryStoreFetched(ipMemoryStore: IIpMemoryStore) {
fetchedFuture.complete(ipMemoryStore)
}
override fun getInterfaceVersion() = IIpMemoryStoreCallbacks.VERSION
override fun getInterfaceHash() = IIpMemoryStoreCallbacks.HASH
}
@JvmStatic @BeforeClass
fun setUpClass() {
// Connect to the NetworkStack only once, as it is relatively expensive (~50ms plus any
// polling time waiting for the test UID to be allowed), and there should be minimal
// side-effects between tests compared to reconnecting every time.
automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS)
try {
automation.executeShellCommand("su root service call network_stack " +
"$ALLOW_TEST_UID_INDEX i32 " + Process.myUid())
// Connecting to the test service does not require allowing the test UID (binding is
// always allowed with NETWORK_SETTINGS permissions on debuggable builds), but
// allowing the test UID is required to call any method on the service.
nsClient = TestNetworkStackServiceClient.connect()
// Wait for oneway call to be processed: unfortunately there is no easy way to wait
// for a success callback via the service shell command.
// TODO: build a small native util that also waits for the success callback, bundle
// it in the test APK, and run it as shell command as root instead.
mStore = getIpMemoryStore()
} finally {
automation.dropShellPermissionIdentity()
}
}
private fun getIpMemoryStore(): NetworkStackIpMemoryStore {
// Until the test UID is allowed, oneway binder calls will not receive any reply.
// Call fetchIpMemoryStore (which has limited side-effects) repeatedly until any call
// gets a callback.
val limit = currentTimeMillis() + TEST_TIMEOUT_MS
val fetchedFuture = CompletableFuture<IIpMemoryStore>()
Log.i(TAG, "Starting multiple attempts to fetch IpMemoryStore; failures are expected")
while (currentTimeMillis() < limit) {
try {
nsClient.fetchIpMemoryStore(IpMemoryStoreCallbacks(fetchedFuture))
// The future may be completed by any previous call to fetchIpMemoryStore.
val ipMemoryStore = fetchedFuture.get(20, TimeUnit.MILLISECONDS)
Log.i(TAG, "Obtained IpMemoryStore: " + ipMemoryStore)
return NetworkStackIpMemoryStore(mContext, ipMemoryStore)
} catch (e: TimeoutException) {
// Fall through
}
}
fail("fail to get the IpMemoryStore instance within timeout")
}
@JvmStatic @AfterClass
fun tearDownClass() {
nsClient.disconnect()
automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS)
try {
// Reset the test UID as -1.
// This may not be called if the test process is terminated before completing,
// however this is fine as the test UID is only usable on userdebug builds, and
// the system does not reuse UIDs across apps until reboot.
automation.executeShellCommand("su root service call network_stack " +
"$ALLOW_TEST_UID_INDEX i32 -1")
} finally {
automation.dropShellPermissionIdentity()
}
}
}
private val originalFlagValues = ArrayMap<String, String>()
/**
* Wrapper class for IIpClientCallbacks.
*
* Used to delegate method calls to mock interfaces used to verify the calls, while using
* real implementations of the binder stub (such as [asBinder]) to properly receive the calls.
*/
private class BinderCbWrapper(base: IIpClientCallbacks) :
IIpClientCallbacks.Stub(), IIpClientCallbacks by base {
// asBinder is implemented by both base class and delegate: specify explicitly
override fun asBinder() = super.asBinder()
override fun getInterfaceVersion() = IIpClientCallbacks.VERSION
}
@After
fun tearDownFlags() {
if (testSkipped()) return
automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
try {
for ((key, value) in originalFlagValues.entries) {
if (key == null) continue
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key,
value, false /* makeDefault */)
}
} finally {
automation.dropShellPermissionIdentity()
}
}
@After
fun tearDownIpMemoryStore() {
if (testSkipped()) return
val latch = CountDownLatch(1)
// Delete the IpMemoryStore entry corresponding to TEST_L2KEY, make sure each test starts
// from a clean state.
mStore.delete(TEST_L2KEY, true) { _, _ -> latch.countDown() }
assertTrue(latch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS))
}
override fun useNetworkStackSignature() = false
override fun makeIIpClient(ifaceName: String, cbMock: IIpClientCallbacks): IIpClient {
val ipClientCaptor = ArgumentCaptor.forClass(IIpClient::class.java)
// Older versions of NetworkStack do not clear the calling identity when creating IpClient,
// so READ_DEVICE_CONFIG is required to initialize it (b/168577898).
automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG)
try {
nsClient.makeIpClient(ifaceName, BinderCbWrapper(cbMock))
verify(cbMock, timeout(TEST_TIMEOUT_MS)).onIpClientCreated(ipClientCaptor.capture())
} finally {
automation.dropShellPermissionIdentity()
}
return ipClientCaptor.value
}
override fun setFeatureEnabled(feature: String, enabled: Boolean) {
automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
try {
// Do not use computeIfAbsent as it would overwrite null values (flag originally unset)
if (!originalFlagValues.containsKey(feature)) {
originalFlagValues[feature] =
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature)
}
// The feature is enabled if the flag is lower than the package version.
// Package versions follow a standard format with 9 digits.
// TODO: consider resetting flag values on reboot when set to special values like "1" or
// "999999999"
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature,
if (enabled) "1" else "999999999", false)
} finally {
automation.dropShellPermissionIdentity()
}
}
override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean {
automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
try {
return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY,
name, defaultEnabled)
} finally {
automation.dropShellPermissionIdentity()
}
}
private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener {
private val future = CompletableFuture<NetworkAttributes?>()
override fun onNetworkAttributesRetrieved(
status: Status,
key: String,
attr: NetworkAttributes?
) {
// NetworkAttributes associated to specific l2key retrieved from IpMemoryStore might be
// null according to testcase context, hence, make sure the callback is triggered with
// success and the l2key param return from callback matches, which also prevents the
// case that the NetworkAttributes haven't been stored within CompletableFuture polling
// timeout.
if (key != TEST_L2KEY || status.resultCode != Status.SUCCESS) {
fail("retrieved the network attributes associated to L2Key: " + key +
" status: " + status.resultCode + " attributes: " + attr)
}
future.complete(attr)
}
fun getBlockingNetworkAttributes(timeout: Long): NetworkAttributes? {
return future.get(timeout, TimeUnit.MILLISECONDS)
}
}
override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes {
val listener = TestAttributesRetrievedListener()
mStore.retrieveNetworkAttributes(l2Key, listener)
val na = listener.getBlockingNetworkAttributes(timeout)
assertNotNull(na)
return na
}
override fun assertIpMemoryNeverStoreNetworkAttributes(l2Key: String, timeout: Long) {
val listener = TestAttributesRetrievedListener()
mStore.retrieveNetworkAttributes(l2Key, listener)
assertNull(listener.getBlockingNetworkAttributes(timeout))
}
override fun assertNotifyNeighborLost(targetIp: Inet6Address) {
verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(anyString())
}
override fun assertNeverNotifyNeighborLost() {
verify(mCb, never()).onReachabilityLost(anyString())
}
override fun storeNetworkAttributes(l2Key: String, na: NetworkAttributes) {
mStore.storeNetworkAttributes(l2Key, na, null /* listener */)
}
}