• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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