1 /* <lambda>null2 * 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.NetworkAttributes 26 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener 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 originalPropertyValues = 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 override fun getInterfaceVersion() = IIpClientCallbacks.VERSION 154 } 155 156 @After 157 fun tearDownDeviceConfigProperties() { 158 if (testSkipped()) return 159 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) 160 try { 161 for ((key, value) in originalPropertyValues.entries) { 162 if (key == null) continue 163 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key, 164 value, false /* makeDefault */) 165 } 166 } finally { 167 automation.dropShellPermissionIdentity() 168 } 169 } 170 171 @After 172 fun tearDownIpMemoryStore() { 173 if (testSkipped()) return 174 val latch = CountDownLatch(1) 175 176 // Delete the IpMemoryStore entry corresponding to TEST_L2KEY, make sure each test starts 177 // from a clean state. 178 mStore.delete(TEST_L2KEY, true) { _, _ -> latch.countDown() } 179 assertTrue(latch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) 180 } 181 182 override fun useNetworkStackSignature() = false 183 184 override fun makeIIpClient(ifaceName: String, cbMock: IIpClientCallbacks): IIpClient { 185 val ipClientCaptor = ArgumentCaptor.forClass(IIpClient::class.java) 186 // Older versions of NetworkStack do not clear the calling identity when creating IpClient, 187 // so READ_DEVICE_CONFIG is required to initialize it (b/168577898). 188 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG) 189 try { 190 nsClient.makeIpClient(ifaceName, BinderCbWrapper(cbMock)) 191 verify(cbMock, timeout(TEST_TIMEOUT_MS)).onIpClientCreated(ipClientCaptor.capture()) 192 } finally { 193 automation.dropShellPermissionIdentity() 194 } 195 return ipClientCaptor.value 196 } 197 198 private fun setDeviceConfigProperty(name: String, value: String) { 199 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) 200 try { 201 // Do not use computeIfAbsent as it would overwrite null values, 202 // property originally unset. 203 if (!originalPropertyValues.containsKey(name)) { 204 originalPropertyValues[name] = 205 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name) 206 } 207 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name, value, 208 false /* makeDefault */) 209 } finally { 210 automation.dropShellPermissionIdentity() 211 } 212 } 213 214 override fun setFeatureEnabled(feature: String, enabled: Boolean) { 215 // The feature is enabled if the flag is lower than the package version. 216 // Package versions follow a standard format with 9 digits. 217 // TODO: consider resetting flag values on reboot when set to special values like "1" or 218 // "999999999" 219 setDeviceConfigProperty(feature, if (enabled) "1" else "999999999") 220 } 221 222 override fun setDeviceConfigProperty(name: String, value: Int) { 223 setDeviceConfigProperty(name, value.toString()) 224 } 225 226 override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean { 227 automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) 228 try { 229 return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY, 230 name, defaultEnabled) 231 } finally { 232 automation.dropShellPermissionIdentity() 233 } 234 } 235 236 private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener { 237 private val future = CompletableFuture<NetworkAttributes?>() 238 override fun onNetworkAttributesRetrieved( 239 status: Status, 240 key: String, 241 attr: NetworkAttributes? 242 ) { 243 // NetworkAttributes associated to specific l2key retrieved from IpMemoryStore might be 244 // null according to testcase context, hence, make sure the callback is triggered with 245 // success and the l2key param return from callback matches, which also prevents the 246 // case that the NetworkAttributes haven't been stored within CompletableFuture polling 247 // timeout. 248 if (key != TEST_L2KEY || status.resultCode != Status.SUCCESS) { 249 fail("retrieved the network attributes associated to L2Key: " + key + 250 " status: " + status.resultCode + " attributes: " + attr) 251 } 252 future.complete(attr) 253 } 254 255 fun getBlockingNetworkAttributes(timeout: Long): NetworkAttributes? { 256 return future.get(timeout, TimeUnit.MILLISECONDS) 257 } 258 } 259 260 override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes { 261 val listener = TestAttributesRetrievedListener() 262 mStore.retrieveNetworkAttributes(l2Key, listener) 263 val na = listener.getBlockingNetworkAttributes(timeout) 264 assertNotNull(na) 265 return na 266 } 267 268 override fun assertIpMemoryNeverStoreNetworkAttributes(l2Key: String, timeout: Long) { 269 val listener = TestAttributesRetrievedListener() 270 mStore.retrieveNetworkAttributes(l2Key, listener) 271 assertNull(listener.getBlockingNetworkAttributes(timeout)) 272 } 273 274 override fun storeNetworkAttributes(l2Key: String, na: NetworkAttributes) { 275 mStore.storeNetworkAttributes(l2Key, na, null /* listener */) 276 } 277 278 private fun readNudSolicitNumFromResource(name: String): Int { 279 val packageName = nsClient.getNetworkStackPackageName() 280 val resource = mContext.createPackageContext(packageName, 0).getResources() 281 val id = resource.getIdentifier(name, "integer", packageName) 282 return resource.getInteger(id) 283 } 284 285 override fun readNudSolicitNumInSteadyStateFromResource(): Int { 286 return readNudSolicitNumFromResource("config_nud_steadystate_solicit_num") 287 } 288 289 override fun readNudSolicitNumPostRoamingFromResource(): Int { 290 return readNudSolicitNumFromResource("config_nud_postroaming_solicit_num") 291 } 292 } 293