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