• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.server.net.integrationtests
18 
19 import android.Manifest.permission
20 import android.app.usage.NetworkStatsManager
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.Context.BIND_AUTO_CREATE
24 import android.content.Context.BIND_IMPORTANT
25 import android.content.Intent
26 import android.content.ServiceConnection
27 import android.content.res.Resources
28 import android.net.ConnectivityManager
29 import android.net.IDnsResolver
30 import android.net.INetd
31 import android.net.INetd.PERMISSION_INTERNET
32 import android.net.LinkProperties
33 import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
34 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
35 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
36 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
37 import android.net.NetworkRequest
38 import android.net.TestNetworkStackClient
39 import android.net.Uri
40 import android.net.metrics.IpConnectivityLog
41 import android.os.ConditionVariable
42 import android.os.Handler
43 import android.os.HandlerThread
44 import android.os.IBinder
45 import android.os.SystemConfigManager
46 import android.os.UserHandle
47 import android.os.VintfRuntimeInfo
48 import android.telephony.TelephonyManager
49 import android.testing.TestableContext
50 import android.util.Log
51 import androidx.test.platform.app.InstrumentationRegistry
52 import com.android.compatibility.common.util.SystemUtil
53 import com.android.connectivity.resources.R
54 import com.android.net.module.util.BpfUtils
55 import com.android.networkstack.apishim.TelephonyManagerShimImpl
56 import com.android.server.BpfNetMaps
57 import com.android.server.ConnectivityService
58 import com.android.server.NetworkAgentWrapper
59 import com.android.server.TestNetIdManager
60 import com.android.server.connectivity.CarrierPrivilegeAuthenticator
61 import com.android.server.connectivity.ConnectivityResources
62 import com.android.server.connectivity.InterfaceTracker
63 import com.android.server.connectivity.MockableSystemProperties
64 import com.android.server.connectivity.MultinetworkPolicyTracker
65 import com.android.server.connectivity.PermissionMonitor
66 import com.android.server.connectivity.ProxyTracker
67 import com.android.server.connectivity.SatelliteAccessController
68 import com.android.testutils.DevSdkIgnoreRunner
69 import com.android.testutils.DeviceInfoUtils
70 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
71 import com.android.testutils.TestableNetworkCallback
72 import com.android.testutils.runAsShell
73 import com.android.testutils.tryTest
74 import java.util.function.BiConsumer
75 import java.util.function.Consumer
76 import kotlin.test.assertEquals
77 import kotlin.test.assertNotNull
78 import kotlin.test.assertTrue
79 import kotlin.test.fail
80 import org.junit.After
81 import org.junit.Assume
82 import org.junit.Before
83 import org.junit.BeforeClass
84 import org.junit.Test
85 import org.junit.runner.RunWith
86 import org.mockito.AdditionalAnswers
87 import org.mockito.ArgumentMatchers.anyString
88 import org.mockito.Mock
89 import org.mockito.Mockito.any
90 import org.mockito.Mockito.anyInt
91 import org.mockito.Mockito.doNothing
92 import org.mockito.Mockito.doReturn
93 import org.mockito.Mockito.eq
94 import org.mockito.Mockito.mock
95 import org.mockito.MockitoAnnotations
96 import org.mockito.Spy
97 
98 const val SERVICE_BIND_TIMEOUT_MS = 5_000L
99 const val TEST_TIMEOUT_MS = 10_000L
100 
101 /**
102  * Test that exercises an instrumented version of ConnectivityService against an instrumented
103  * NetworkStack in a different test process.
104  */
105 @RunWith(DevSdkIgnoreRunner::class)
106 @DevSdkIgnoreRunner.MonitorThreadLeak
107 class ConnectivityServiceIntegrationTest {
108     // lateinit used here for mocks as they need to be reinitialized between each test and the test
109     // should crash if they are used before being initialized.
110     @Mock
111     private lateinit var statsManager: NetworkStatsManager
112     @Mock
113     private lateinit var log: IpConnectivityLog
114     @Mock
115     private lateinit var netd: INetd
116     @Mock
117     private lateinit var interfaceTracker: InterfaceTracker
118     @Mock
119     private lateinit var dnsResolver: IDnsResolver
120     @Mock
121     private lateinit var systemConfigManager: SystemConfigManager
122     @Mock
123     private lateinit var resources: Resources
124     @Mock
125     private lateinit var resourcesContext: Context
126     @Spy
127     private var context = TestableContext(realContext)
128 
129     // lateinit for these three classes under test, as they should be reset to a different instance
130     // for every test but should always be initialized before use (or the test should crash).
131     private lateinit var networkStackClient: TestNetworkStackClient
132     private lateinit var service: ConnectivityService
133     private lateinit var cm: ConnectivityManager
134 
135     private val handlerThreads = mutableListOf<HandlerThread>()
136 
137     companion object {
138         // lateinit for this binder token, as it must be initialized before any test code is run
139         // and use of it before init should crash the test.
140         private lateinit var nsInstrumentation: INetworkStackInstrumentation
141         private val bindingCondition = ConditionVariable(false)
142 
143         private val realContext get() = InstrumentationRegistry.getInstrumentation().context
144         private val httpProbeUrl get() =
145             realContext.getResources().getString(
146                 com.android.server.net.integrationtests.R.string
147                     .config_captive_portal_http_url
148             )
149         private val httpsProbeUrl get() =
150             realContext.getResources().getString(
151                 com.android.server.net.integrationtests.R.string
152                     .config_captive_portal_https_url
153             )
154 
155         private class InstrumentationServiceConnection : ServiceConnection {
onServiceConnectednull156             override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
157                 Log.i("TestNetworkStack", "Service connected")
158                 try {
159                     if (service == null) fail("Error binding to NetworkStack instrumentation")
160                     if (::nsInstrumentation.isInitialized) fail("Service already connected")
161                     nsInstrumentation = INetworkStackInstrumentation.Stub.asInterface(service)
162                 } finally {
163                     bindingCondition.open()
164                 }
165             }
166 
onServiceDisconnectednull167             override fun onServiceDisconnected(name: ComponentName?) = Unit
168         }
169 
170         @BeforeClass
171         @JvmStatic
172         fun setUpClass() {
173             val intent = Intent(realContext, NetworkStackInstrumentationService::class.java)
174             intent.action = INetworkStackInstrumentation::class.qualifiedName
175             assertTrue(
176                 realContext.bindService(
177                     intent,
178                     InstrumentationServiceConnection(),
179                     BIND_AUTO_CREATE or BIND_IMPORTANT
180                 ),
181                     "Error binding to instrumentation service"
182             )
183             assertTrue(
184                 bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
185                     "Timed out binding to instrumentation service " +
186                             "after $SERVICE_BIND_TIMEOUT_MS ms"
187             )
188         }
189     }
190 
191     @Before
setUpnull192     fun setUp() {
193         MockitoAnnotations.initMocks(this)
194         val asUserCtx = mock(Context::class.java, AdditionalAnswers.delegatesTo<Context>(context))
195         doReturn(UserHandle.ALL).`when`(asUserCtx).user
196         doReturn(asUserCtx).`when`(context).createContextAsUser(eq(UserHandle.ALL), anyInt())
197         doNothing().`when`(context).sendStickyBroadcast(any(), any())
198         doReturn(Context.SYSTEM_CONFIG_SERVICE).`when`(context)
199                 .getSystemServiceName(SystemConfigManager::class.java)
200         doReturn(systemConfigManager).`when`(context)
201                 .getSystemService(Context.SYSTEM_CONFIG_SERVICE)
202         doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
203 
204         doReturn(60000).`when`(resources).getInteger(R.integer.config_networkTransitionTimeout)
205         doReturn("").`when`(resources).getString(R.string.config_networkCaptivePortalServerUrl)
206         doReturn(arrayOf<String>("test_wlan_wol")).`when`(resources)
207                 .getStringArray(R.array.config_wakeonlan_supported_interfaces)
208         doReturn(arrayOf("0,1", "1,3")).`when`(resources)
209                 .getStringArray(R.array.config_networkSupportedKeepaliveCount)
210         doReturn(emptyArray<String>()).`when`(resources)
211                 .getStringArray(R.array.config_networkNotifySwitches)
212         doReturn(intArrayOf(10, 11, 12, 14, 15)).`when`(resources)
213                 .getIntArray(R.array.config_protectedNetworks)
214         // We don't test the actual notification value strings, so just return an empty array.
215         // It doesn't matter what the values are as long as it's not null.
216         doReturn(emptyArray<String>()).`when`(resources).getStringArray(
217                 R.array.network_switch_type_name
218         )
219         doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
220         doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(resources)
221                 .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
222 
223         doReturn(resources).`when`(resourcesContext).getResources()
224         ConnectivityResources.setResourcesContextForTest(resourcesContext)
225 
226         networkStackClient = TestNetworkStackClient(realContext)
227         networkStackClient.start()
228 
229         service = runAsShell(permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) {
230             TestConnectivityService(TestDependencies())
231         }
232         cm = ConnectivityManager(context, service)
233         context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm)
234         context.addMockSystemService(Context.NETWORK_STATS_SERVICE, statsManager)
235 
236         service.systemReadyInternal()
237     }
238 
239     private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
240             context,
241         dnsResolver,
242         log,
243         netd,
244         deps,
245         PermissionMonitorDependencies()
246     )
247 
248     private inner class TestDependencies : ConnectivityService.Dependencies() {
getNetworkStacknull249         override fun getNetworkStack() = networkStackClient
250         override fun makeProxyTracker(context: Context, connServiceHandler: Handler) =
251             mock(ProxyTracker::class.java)
252         override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
253         override fun makeNetIdManager() = TestNetIdManager()
254         override fun getBpfNetMaps(
255             context: Context?,
256             netd: INetd?,
257                                    interfaceTracker: InterfaceTracker?
258         ) = mock(BpfNetMaps::class.java)
259                 .also {
260                     doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
261                 }
isChangeEnablednull262         override fun isChangeEnabled(changeId: Long, uid: Int) = true
263 
264         override fun makeMultinetworkPolicyTracker(
265             c: Context,
266             h: Handler,
267             r: Runnable
268         ) = MultinetworkPolicyTracker(
269             c,
270             h,
271             r,
272             object : MultinetworkPolicyTracker.Dependencies() {
273                 override fun getResourcesForActiveSubId(
274                     connResources: ConnectivityResources,
275                     activeSubId: Int
276                 ) = resources
277             }
278         )
279 
makeHandlerThreadnull280         override fun makeHandlerThread(tag: String): HandlerThread =
281             super.makeHandlerThread(tag).also { handlerThreads.add(it) }
282 
makeCarrierPrivilegeAuthenticatornull283         override fun makeCarrierPrivilegeAuthenticator(
284                 context: Context,
285                 tm: TelephonyManager,
286                 requestRestrictedWifiEnabled: Boolean,
287                 listener: BiConsumer<Int, Int>,
288                 handler: Handler
289         ): CarrierPrivilegeAuthenticator {
290             return CarrierPrivilegeAuthenticator(
291                 context,
292                     object : CarrierPrivilegeAuthenticator.Dependencies() {
293                         override fun makeHandlerThread(): HandlerThread =
294                                 super.makeHandlerThread().also { handlerThreads.add(it) }
295                     },
296                     tm,
297                 TelephonyManagerShimImpl.newInstance(tm),
298                     requestRestrictedWifiEnabled,
299                 listener,
300                 handler
301             )
302         }
303 
makeSatelliteAccessControllernull304         override fun makeSatelliteAccessController(
305             context: Context,
306             updateSatellitePreferredUid: Consumer<MutableSet<Int>>?,
307             connectivityServiceInternalHandler: Handler
308         ): SatelliteAccessController? = mock(
309             SatelliteAccessController::class.java
310         )
311 
312         override fun makeL2capNetworkProvider(context: Context) = null
313     }
314 
315     private inner class PermissionMonitorDependencies : PermissionMonitor.Dependencies() {
316         override fun shouldEnforceLocalNetRestrictions(uid: Int) = false
317     }
318 
319     @After
tearDownnull320     fun tearDown() {
321         nsInstrumentation.clearAllState()
322         ConnectivityResources.setResourcesContextForTest(null)
323         handlerThreads.forEach {
324             it.quitSafely()
325             it.join()
326         }
327         handlerThreads.clear()
328     }
329 
330     @Test
testValidationnull331     fun testValidation() {
332         val request = NetworkRequest.Builder()
333                 .clearCapabilities()
334                 .addCapability(NET_CAPABILITY_INTERNET)
335                 .build()
336         val testCallback = TestableNetworkCallback()
337 
338         cm.registerNetworkCallback(request, testCallback)
339         nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
340         nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
341 
342         val na = NetworkAgentWrapper(
343             TRANSPORT_CELLULAR,
344             LinkProperties(),
345             null /* ncTemplate */,
346                 context
347         )
348         networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
349 
350         na.addCapability(NET_CAPABILITY_INTERNET)
351         na.connect()
352 
353         tryTest {
354             testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
355             val requestedSize = nsInstrumentation.getRequestUrls().size
356             if (requestedSize == 2 || (requestedSize == 1 &&
357                         nsInstrumentation.getRequestUrls()[0] == httpsProbeUrl)
358             ) {
359                 return@tryTest
360             }
361             fail("Unexpected request urls: ${nsInstrumentation.getRequestUrls()}")
362         } cleanup {
363             na.destroy()
364         }
365     }
366 
367     @Test
testCapportApinull368     fun testCapportApi() {
369         val request = NetworkRequest.Builder()
370                 .clearCapabilities()
371                 .addCapability(NET_CAPABILITY_INTERNET)
372                 .build()
373         val testCb = TestableNetworkCallback()
374         val apiUrl = "https://capport.android.com"
375 
376         cm.registerNetworkCallback(request, testCb)
377         nsInstrumentation.addHttpResponse(HttpResponse(
378                 apiUrl,
379                 """
380                     |{
381                     |  "captive": true,
382                     |  "user-portal-url": "https://login.capport.android.com",
383                     |  "venue-info-url": "https://venueinfo.capport.android.com"
384                     |}
385                 """.trimMargin()
386         ))
387 
388         // Tests will fail if a non-mocked query is received: mock the HTTPS probe, but not the
389         // HTTP probe as it should not be sent.
390         // Even if the HTTPS probe succeeds, a portal should be detected as the API takes precedence
391         // in that case.
392         nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
393 
394         val lp = LinkProperties()
395         lp.captivePortalApiUrl = Uri.parse(apiUrl)
396         val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, null /* ncTemplate */, context)
397 
398         tryTest {
399             networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
400 
401             na.addCapability(NET_CAPABILITY_INTERNET)
402             na.connect()
403 
404             testCb.expectAvailableCallbacks(na.network, validated = false, tmt = TEST_TIMEOUT_MS)
405 
406             val capportData = testCb.expect<LinkPropertiesChanged>(na, TEST_TIMEOUT_MS) {
407                 it.lp.captivePortalData != null
408             }.lp.captivePortalData
409             assertNotNull(capportData)
410             assertTrue(capportData.isCaptive)
411             assertEquals(Uri.parse("https://login.capport.android.com"), capportData.userPortalUrl)
412             assertEquals(
413                 Uri.parse("https://venueinfo.capport.android.com"),
414                 capportData.venueInfoUrl
415             )
416 
417             testCb.expectCaps(na, TEST_TIMEOUT_MS) {
418                 it.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) &&
419                         !it.hasCapability(NET_CAPABILITY_VALIDATED)
420             }
421         } cleanup {
422             na.destroy()
423         }
424     }
425 
isBpfGetCgroupProgramIdSupportedByKernelnull426     private fun isBpfGetCgroupProgramIdSupportedByKernel(): Boolean {
427         val kVersionString = VintfRuntimeInfo.getKernelVersion()
428         return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.19") >= 0
429     }
430 
431     @Test
testBpfProgramAttachStatusnull432     fun testBpfProgramAttachStatus() {
433         Assume.assumeTrue(isBpfGetCgroupProgramIdSupportedByKernel())
434 
435         listOf(
436                 BpfUtils.BPF_CGROUP_INET_INGRESS,
437                 BpfUtils.BPF_CGROUP_INET_EGRESS,
438                 BpfUtils.BPF_CGROUP_INET_SOCK_CREATE
439         ).forEach {
440             val ret = SystemUtil.runShellCommand(
441                 InstrumentationRegistry.getInstrumentation(),
442                     "cmd connectivity bpf-get-cgroup-program-id $it"
443             ).trim()
444 
445             assertTrue(Integer.parseInt(ret) > 0, "Unexpected output $ret for type $it")
446         }
447     }
448 }
449