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