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.permission.INTERACT_ACROSS_USERS_FULL
20 import android.Manifest.permission.NETWORK_SETTINGS
21 import android.Manifest.permission.READ_DEVICE_CONFIG
22 import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
23 import android.Manifest.permission.WRITE_DEVICE_CONFIG
24 import android.net.IIpMemoryStore
25 import android.net.IIpMemoryStoreCallbacks
26 import android.net.NetworkStackIpMemoryStore
27 import android.net.ipmemorystore.NetworkAttributes
28 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener
29 import android.net.ipmemorystore.OnNetworkEventCountRetrievedListener
30 import android.net.ipmemorystore.Status
31 import android.net.networkstack.TestNetworkStackServiceClient
32 import android.os.Process
33 import android.provider.DeviceConfig
34 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
35 import android.util.ArrayMap
36 import android.util.Log
37 import androidx.test.platform.app.InstrumentationRegistry
38 import java.lang.System.currentTimeMillis
39 import java.lang.UnsupportedOperationException
40 import java.util.concurrent.CompletableFuture
41 import java.util.concurrent.CountDownLatch
42 import java.util.concurrent.TimeUnit
43 import java.util.concurrent.TimeoutException
44 import kotlin.test.assertFalse
45 import kotlin.test.assertNotNull
46 import kotlin.test.assertNull
47 import kotlin.test.assertTrue
48 import kotlin.test.fail
49 import org.junit.After
50 import org.junit.AfterClass
51 import org.junit.BeforeClass
52 import org.mockito.ArgumentCaptor
53 import org.mockito.Mockito.timeout
54 import org.mockito.Mockito.verify
55 
56 // Stable AIDL method 5 in INetworkStackConnector is allowTestUid
57 private const val ALLOW_TEST_UID_INDEX = 5
58 
59 /**
60  * Tests for IpClient, run with root access but no signature permissions.
61  *
62  * Tests run from this class interact with the real network stack process and can affect system
63  * state, e.g. by changing flags.
64  * State should be restored at the end of the test, but is not guaranteed if the test process is
65  * terminated during the run.
66  */
67 class IpClientRootTest : IpClientIntegrationTestCommon() {
68     companion object {
69         private val TAG = IpClientRootTest::class.java.simpleName
70         private val automation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
71         private lateinit var nsClient: TestNetworkStackServiceClient
72         private lateinit var mStore: NetworkStackIpMemoryStore
73         private val mContext = InstrumentationRegistry.getInstrumentation().getContext()
74 
75         private class IpMemoryStoreCallbacks(
76             private val fetchedFuture: CompletableFuture<IIpMemoryStore>
77         ) : IIpMemoryStoreCallbacks.Stub() {
78             override fun onIpMemoryStoreFetched(ipMemoryStore: IIpMemoryStore) {
79                 fetchedFuture.complete(ipMemoryStore)
80             }
81             override fun getInterfaceVersion() = IIpMemoryStoreCallbacks.VERSION
82             override fun getInterfaceHash() = IIpMemoryStoreCallbacks.HASH
83         }
84 
85         @JvmStatic @BeforeClass
86         fun setUpClass() {
87             // Connect to the NetworkStack only once, as it is relatively expensive (~50ms plus any
88             // polling time waiting for the test UID to be allowed), and there should be minimal
89             // side-effects between tests compared to reconnecting every time.
90             automation.adoptShellPermissionIdentity(NETWORK_SETTINGS, INTERACT_ACROSS_USERS_FULL)
91             try {
92                 automation.executeShellCommand("su root service call network_stack " +
93                         "$ALLOW_TEST_UID_INDEX i32 " + Process.myUid())
94                 // Connecting to the test service does not require allowing the test UID (binding is
95                 // always allowed with NETWORK_SETTINGS permissions on debuggable builds), but
96                 // allowing the test UID is required to call any method on the service.
97                 nsClient = TestNetworkStackServiceClient.connect()
98                 // Wait for oneway call to be processed: unfortunately there is no easy way to wait
99                 // for a success callback via the service shell command.
100                 // TODO: build a small native util that also waits for the success callback, bundle
101                 // it in the test APK, and run it as shell command as root instead.
102                 mStore = getIpMemoryStore()
103             } finally {
104                 automation.dropShellPermissionIdentity()
105             }
106         }
107 
108         private fun getIpMemoryStore(): NetworkStackIpMemoryStore {
109             // Until the test UID is allowed, oneway binder calls will not receive any reply.
110             // Call fetchIpMemoryStore (which has limited side-effects) repeatedly until any call
111             // gets a callback.
112             val limit = currentTimeMillis() + TEST_TIMEOUT_MS
113             val fetchedFuture = CompletableFuture<IIpMemoryStore>()
114             Log.i(TAG, "Starting multiple attempts to fetch IpMemoryStore; failures are expected")
115             while (currentTimeMillis() < limit) {
116                 try {
117                     nsClient.fetchIpMemoryStore(IpMemoryStoreCallbacks(fetchedFuture))
118                     // The future may be completed by any previous call to fetchIpMemoryStore.
119                     val ipMemoryStore = fetchedFuture.get(20, TimeUnit.MILLISECONDS)
120                     Log.i(TAG, "Obtained IpMemoryStore: " + ipMemoryStore)
121                     return NetworkStackIpMemoryStore(mContext, ipMemoryStore)
122                 } catch (e: TimeoutException) {
123                     // Fall through
124                 }
125             }
126             fail("fail to get the IpMemoryStore instance within timeout")
127         }
128 
129         @JvmStatic @AfterClass
130         fun tearDownClass() {
131             nsClient.disconnect()
132             automation.adoptShellPermissionIdentity(NETWORK_SETTINGS)
133             try {
134                 // Reset the test UID as -1.
135                 // This may not be called if the test process is terminated before completing,
136                 // however this is fine as the test UID is only usable on userdebug builds, and
137                 // the system does not reuse UIDs across apps until reboot.
138                 automation.executeShellCommand("su root service call network_stack " +
139                         "$ALLOW_TEST_UID_INDEX i32 -1")
140             } finally {
141                 automation.dropShellPermissionIdentity()
142             }
143         }
144     }
145 
146     /**
147      * Wrapper class for IIpClientCallbacks.
148      *
149      * Used to delegate method calls to mock interfaces used to verify the calls, while using
150      * real implementations of the binder stub (such as [asBinder]) to properly receive the calls.
151      */
152     private class BinderCbWrapper(base: IIpClientCallbacks) :
153             IIpClientCallbacks.Stub(), IIpClientCallbacks by base {
154         // asBinder is implemented by both base class and delegate: specify explicitly
155         override fun asBinder() = super.asBinder()
156         override fun getInterfaceVersion() = IIpClientCallbacks.VERSION
157     }
158 
159     @After
160     fun tearDownIpMemoryStore() {
161         if (testSkipped()) return
162         val latch = CountDownLatch(1)
163 
164         // Delete the IpMemoryStore entry corresponding to TEST_L2KEY, make sure each test starts
165         // from a clean state.
166         mStore.delete(TEST_L2KEY, true) { _, _ -> latch.countDown() }
167         assertTrue(latch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS))
168     }
169 
170     override fun useNetworkStackSignature() = false
171 
172     override fun makeIIpClient(ifaceName: String, cbMock: IIpClientCallbacks): IIpClient {
173         val ipClientCaptor = ArgumentCaptor.forClass(IIpClient::class.java)
174         // Older versions of NetworkStack do not clear the calling identity when creating IpClient,
175         // so READ_DEVICE_CONFIG is required to initialize it (b/168577898).
176         automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG)
177         try {
178             nsClient.makeIpClient(ifaceName, BinderCbWrapper(cbMock))
179             verify(cbMock, timeout(TEST_TIMEOUT_MS)).onIpClientCreated(ipClientCaptor.capture())
180         } finally {
181             automation.dropShellPermissionIdentity()
182         }
183         return ipClientCaptor.value
184     }
185 
186     // These are not needed in IpClientRootTest because there is no dependency injection and
187     // IpClient always uses the production implementations.
188     override fun getDeviceConfigProperty(name: String) = throw UnsupportedOperationException()
189     override fun isFeatureEnabled(name: String) = throw UnsupportedOperationException()
190     override fun isFeatureNotChickenedOut(name: String) = throw UnsupportedOperationException()
191 
192     private val mOriginalPropertyValues = ArrayMap<String, String>()
193 
194     override fun setDeviceConfigProperty(name: String?, value: String?) {
195         automation.adoptShellPermissionIdentity(
196             READ_DEVICE_CONFIG,
197             WRITE_DEVICE_CONFIG,
198             WRITE_ALLOWLISTED_DEVICE_CONFIG
199         )
200         try {
201             // Do not use computeIfAbsent as it would overwrite null values,
202             // property originally unset.
203             if (!mOriginalPropertyValues.containsKey(name)) {
204                 mOriginalPropertyValues[name] = DeviceConfig.getProperty(
205                     DeviceConfig.NAMESPACE_CONNECTIVITY,
206                     (name)!!
207                 )
208             }
209             DeviceConfig.setProperty(
210                 DeviceConfig.NAMESPACE_CONNECTIVITY,
211                 (name)!!, value,
212                 false /* makeDefault */
213             )
214         } finally {
215             automation.dropShellPermissionIdentity()
216         }
217     }
218 
219     @After
220     fun tearDownDeviceConfigProperties() {
221         if (testSkipped()) return
222         automation.adoptShellPermissionIdentity(
223             READ_DEVICE_CONFIG,
224             WRITE_DEVICE_CONFIG,
225             WRITE_ALLOWLISTED_DEVICE_CONFIG
226         )
227         try {
228             for (key in mOriginalPropertyValues.keys) {
229                 if (key == null) continue
230                 DeviceConfig.setProperty(
231                     DeviceConfig.NAMESPACE_CONNECTIVITY, key,
232                     mOriginalPropertyValues[key], false /* makeDefault */
233                 )
234             }
235         } finally {
236             automation.dropShellPermissionIdentity()
237         }
238     }
239 
240     private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener {
241         private val future = CompletableFuture<NetworkAttributes?>()
242         override fun onNetworkAttributesRetrieved(
243             status: Status,
244             key: String,
245             attr: NetworkAttributes?
246         ) {
247             // NetworkAttributes associated to specific l2key retrieved from IpMemoryStore might be
248             // null according to testcase context, hence, make sure the callback is triggered with
249             // success and the l2key param return from callback matches, which also prevents the
250             // case that the NetworkAttributes haven't been stored within CompletableFuture polling
251             // timeout.
252             if (key != TEST_L2KEY || status.resultCode != Status.SUCCESS) {
253                 fail("retrieved the network attributes associated to L2Key: " + key +
254                         " status: " + status.resultCode + " attributes: " + attr)
255             }
256             future.complete(attr)
257         }
258 
259         fun getBlockingNetworkAttributes(timeout: Long): NetworkAttributes? {
260             return future.get(timeout, TimeUnit.MILLISECONDS)
261         }
262     }
263 
264     private class TestNetworkEventCountRetrievedListener : OnNetworkEventCountRetrievedListener {
265         private val future = CompletableFuture<IntArray>()
266         override fun onNetworkEventCountRetrieved(
267             status: Status,
268             counts: IntArray
269         ) {
270             if (status.resultCode != Status.SUCCESS) {
271                 fail("retrieved the network event count " + " status: " + status.resultCode)
272             }
273             future.complete(counts)
274         }
275 
276         fun getBlockingNetworkEventCount(timeout: Long): IntArray {
277             return future.get(timeout, TimeUnit.MILLISECONDS)
278         }
279     }
280 
281     override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes {
282         val listener = TestAttributesRetrievedListener()
283         mStore.retrieveNetworkAttributes(l2Key, listener)
284         val na = listener.getBlockingNetworkAttributes(timeout)
285         assertNotNull(na)
286         return na
287     }
288 
289     override fun assertIpMemoryNeverStoreNetworkAttributes(l2Key: String, timeout: Long) {
290         val listener = TestAttributesRetrievedListener()
291         mStore.retrieveNetworkAttributes(l2Key, listener)
292         assertNull(listener.getBlockingNetworkAttributes(timeout))
293     }
294 
295     override fun storeNetworkAttributes(l2Key: String, na: NetworkAttributes) {
296         mStore.storeNetworkAttributes(l2Key, na, null /* listener */)
297     }
298 
299     override fun storeNetworkEvent(cluster: String, now: Long, expiry: Long, eventType: Int) {
300         mStore.storeNetworkEvent(cluster, now, expiry, eventType, null /* listener */)
301     }
302 
303     override fun getStoredNetworkEventCount(
304             cluster: String,
305             sinceTimes: LongArray,
306             eventType: IntArray,
307             timeout: Long
308     ): IntArray {
309         val listener = TestNetworkEventCountRetrievedListener()
310         mStore.retrieveNetworkEventCount(cluster, sinceTimes, eventType, listener)
311         val counts = listener.getBlockingNetworkEventCount(timeout)
312         assertFalse(counts.size == 0)
313         return counts
314     }
315 
316     private fun readNudSolicitNumFromResource(name: String): Int {
317         val packageName = nsClient.getNetworkStackPackageName()
318         val resource = mContext.createPackageContext(packageName, 0).getResources()
319         val id = resource.getIdentifier(name, "integer", packageName)
320         return resource.getInteger(id)
321     }
322 
323     override fun readNudSolicitNumInSteadyStateFromResource(): Int {
324         return readNudSolicitNumFromResource("config_nud_steadystate_solicit_num")
325     }
326 
327     override fun readNudSolicitNumPostRoamingFromResource(): Int {
328         return readNudSolicitNumFromResource("config_nud_postroaming_solicit_num")
329     }
330 }
331