1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.ip 18 19 import android.Manifest 20 import android.Manifest.permission.READ_DEVICE_CONFIG 21 import android.Manifest.permission.WRITE_DEVICE_CONFIG 22 import android.net.IIpMemoryStore 23 import android.net.IIpMemoryStoreCallbacks 24 import android.net.NetworkStackIpMemoryStore 25 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener 26 import android.net.ipmemorystore.NetworkAttributes 27 import android.net.ipmemorystore.Status 28 import android.net.networkstack.TestNetworkStackServiceClient 29 import android.os.Process 30 import android.provider.DeviceConfig 31 import android.util.ArrayMap 32 import android.util.Log 33 import androidx.test.platform.app.InstrumentationRegistry 34 import com.android.net.module.util.DeviceConfigUtils 35 import java.lang.System.currentTimeMillis 36 import java.util.concurrent.CompletableFuture 37 import java.util.concurrent.CountDownLatch 38 import java.util.concurrent.TimeUnit 39 import java.util.concurrent.TimeoutException 40 import kotlin.test.assertNotNull 41 import kotlin.test.assertNull 42 import kotlin.test.assertTrue 43 import kotlin.test.fail 44 import org.junit.After 45 import org.junit.AfterClass 46 import org.junit.BeforeClass 47 import org.mockito.ArgumentCaptor 48 import org.mockito.Mockito.timeout 49 import org.mockito.Mockito.verify 50 51 // Stable AIDL method 5 in INetworkStackConnector is allowTestUid 52 private const val ALLOW_TEST_UID_INDEX = 5 53 54 /** 55 * Tests for IpClient, run with root access but no signature permissions. 56 * 57 * Tests run from this class interact with the real network stack process and can affect system 58 * state, e.g. by changing flags. 59 * State should be restored at the end of the test, but is not guaranteed if the test process is 60 * terminated during the run. 61 */ 62 class IpClientRootTest : IpClientIntegrationTestCommon() { 63 companion object { 64 private val TAG = IpClientRootTest::class.java.simpleName 65 private val automation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation } 66 private lateinit var nsClient: TestNetworkStackServiceClient 67 private lateinit var mStore: NetworkStackIpMemoryStore 68 private val mContext = InstrumentationRegistry.getInstrumentation().getContext() 69 70 private class IpMemoryStoreCallbacks( 71 private val fetchedFuture: CompletableFuture<IIpMemoryStore> 72 ) : IIpMemoryStoreCallbacks.Stub() { 73 override fun onIpMemoryStoreFetched(ipMemoryStore: IIpMemoryStore) { 74 fetchedFuture.complete(ipMemoryStore) 75 } 76 override fun getInterfaceVersion() = IIpMemoryStoreCallbacks.VERSION 77 override fun getInterfaceHash() = IIpMemoryStoreCallbacks.HASH 78 } 79 80 @JvmStatic @BeforeClass 81 fun setUpClass() { 82 // Connect to the NetworkStack only once, as it is relatively expensive (~50ms plus any 83 // polling time waiting for the test UID to be allowed), and there should be minimal 84 // side-effects between tests compared to reconnecting every time. 85 automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS) 86 try { 87 automation.executeShellCommand("su root service call network_stack " + 88 "$ALLOW_TEST_UID_INDEX i32 " + Process.myUid()) 89 // Connecting to the test service does not require allowing the test UID (binding is 90 // always allowed with NETWORK_SETTINGS permissions on debuggable builds), but 91 // allowing the test UID is required to call any method on the service. 92 nsClient = TestNetworkStackServiceClient.connect() 93 // Wait for oneway call to be processed: unfortunately there is no easy way to wait 94 // for a success callback via the service shell command. 95 // TODO: build a small native util that also waits for the success callback, bundle 96 // it in the test APK, and run it as shell command as root instead. 97 mStore = getIpMemoryStore() 98 } finally { 99 automation.dropShellPermissionIdentity() 100 } 101 } 102 103 private fun getIpMemoryStore(): NetworkStackIpMemoryStore { 104 // Until the test UID is allowed, oneway binder calls will not receive any reply. 105 // Call fetchIpMemoryStore (which has limited side-effects) repeatedly until any call 106 // gets a callback. 107 val limit = currentTimeMillis() + TEST_TIMEOUT_MS 108 val fetchedFuture = CompletableFuture<IIpMemoryStore>() 109 Log.i(TAG, "Starting multiple attempts to fetch IpMemoryStore; failures are expected") 110 while (currentTimeMillis() < limit) { 111 try { 112 nsClient.fetchIpMemoryStore(IpMemoryStoreCallbacks(fetchedFuture)) 113 // The future may be completed by any previous call to fetchIpMemoryStore. 114 val ipMemoryStore = fetchedFuture.get(20, TimeUnit.MILLISECONDS) 115 Log.i(TAG, "Obtained IpMemoryStore: " + ipMemoryStore) 116 return NetworkStackIpMemoryStore(mContext, ipMemoryStore) 117 } catch (e: TimeoutException) { 118 // Fall through 119 } 120 } 121 fail("fail to get the IpMemoryStore instance within timeout") 122 } 123 124 @JvmStatic @AfterClass 125 fun tearDownClass() { 126 nsClient.disconnect() 127 automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS) 128 try { 129 // Reset the test UID as -1. 130 // This may not be called if the test process is terminated before completing, 131 // however this is fine as the test UID is only usable on userdebug builds, and 132 // the system does not reuse UIDs across apps until reboot. 133 automation.executeShellCommand("su root service call network_stack " + 134 "$ALLOW_TEST_UID_INDEX i32 -1") 135 } finally { 136 automation.dropShellPermissionIdentity() 137 } 138 } 139 } 140 141 private val originalFlagValues = ArrayMap<String, String>() 142 143 /** 144 * Wrapper class for IIpClientCallbacks. 145 * 146 * Used to delegate method calls to mock interfaces used to verify the calls, while using 147 * real implementations of the binder stub (such as [asBinder]) to properly receive the calls. 148 */ 149 private class BinderCbWrapper(base: IIpClientCallbacks) : 150 IIpClientCallbacks.Stub(), IIpClientCallbacks by base { 151 // asBinder is implemented by both base class and delegate: specify explicitly 152 override fun asBinder() = super.asBinder() 153 } 154 155 @After 156 fun tearDownFlags() { 157 if (testSkipped()) return 158 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) 159 try { 160 for ((key, value) in originalFlagValues.entries) { 161 if (key == null) continue 162 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key, 163 value, false /* makeDefault */) 164 } 165 } finally { 166 automation.dropShellPermissionIdentity() 167 } 168 } 169 170 @After 171 fun tearDownIpMemoryStore() { 172 if (testSkipped()) return 173 val latch = CountDownLatch(1) 174 175 // Delete the IpMemoryStore entry corresponding to TEST_L2KEY, make sure each test starts 176 // from a clean state. 177 mStore.delete(TEST_L2KEY, true) { _, _ -> latch.countDown() } 178 assertTrue(latch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) 179 } 180 181 override fun useNetworkStackSignature() = false 182 183 override fun makeIIpClient(ifaceName: String, cbMock: IIpClientCallbacks): IIpClient { 184 val ipClientCaptor = ArgumentCaptor.forClass(IIpClient::class.java) 185 // Older versions of NetworkStack do not clear the calling identity when creating IpClient, 186 // so READ_DEVICE_CONFIG is required to initialize it (b/168577898). 187 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG) 188 try { 189 nsClient.makeIpClient(ifaceName, BinderCbWrapper(cbMock)) 190 verify(cbMock, timeout(TEST_TIMEOUT_MS)).onIpClientCreated(ipClientCaptor.capture()) 191 } finally { 192 automation.dropShellPermissionIdentity() 193 } 194 return ipClientCaptor.value 195 } 196 197 override fun setFeatureEnabled(feature: String, enabled: Boolean) { 198 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) 199 try { 200 // Do not use computeIfAbsent as it would overwrite null values (flag originally unset) 201 if (!originalFlagValues.containsKey(feature)) { 202 originalFlagValues[feature] = 203 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature) 204 } 205 // The feature is enabled if the flag is lower than the package version. 206 // Package versions follow a standard format with 9 digits. 207 // TODO: consider resetting flag values on reboot when set to special values like "1" or 208 // "999999999" 209 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature, 210 if (enabled) "1" else "999999999", false) 211 } finally { 212 automation.dropShellPermissionIdentity() 213 } 214 } 215 216 override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean { 217 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) 218 try { 219 return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY, 220 name, defaultEnabled) 221 } finally { 222 automation.dropShellPermissionIdentity() 223 } 224 } 225 226 private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener { 227 private val future = CompletableFuture<NetworkAttributes?>() 228 override fun onNetworkAttributesRetrieved( 229 status: Status, 230 key: String, 231 attr: NetworkAttributes? 232 ) { 233 // NetworkAttributes associated to specific l2key retrieved from IpMemoryStore might be 234 // null according to testcase context, hence, make sure the callback is triggered with 235 // success and the l2key param return from callback matches, which also prevents the 236 // case that the NetworkAttributes haven't been stored within CompletableFuture polling 237 // timeout. 238 if (key != TEST_L2KEY || status.resultCode != Status.SUCCESS) { 239 fail("retrieved the network attributes associated to L2Key: " + key + 240 " status: " + status.resultCode + " attributes: " + attr) 241 } 242 future.complete(attr) 243 } 244 245 fun getBlockingNetworkAttributes(timeout: Long): NetworkAttributes? { 246 return future.get(timeout, TimeUnit.MILLISECONDS) 247 } 248 } 249 250 override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes { 251 val listener = TestAttributesRetrievedListener() 252 mStore.retrieveNetworkAttributes(l2Key, listener) 253 val na = listener.getBlockingNetworkAttributes(timeout) 254 assertNotNull(na) 255 return na 256 } 257 258 override fun assertIpMemoryNeverStoreNetworkAttributes(l2Key: String, timeout: Long) { 259 val listener = TestAttributesRetrievedListener() 260 mStore.retrieveNetworkAttributes(l2Key, listener) 261 assertNull(listener.getBlockingNetworkAttributes(timeout)) 262 } 263 } 264