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