Merge "Add utilities for network validation testing" am: ff15197c34 am: fe093f509b
Original change: https://android-review.googlesource.com/c/platform/cts/+/1364518 Change-Id: I668fe775f984cdbef56bb21e72305571bc8a48bc
This commit is contained in:
@@ -47,7 +47,6 @@ java_defaults {
|
|||||||
"ctstestserver",
|
"ctstestserver",
|
||||||
"junit",
|
"junit",
|
||||||
"junit-params",
|
"junit-params",
|
||||||
"libnanohttpd",
|
|
||||||
"mockwebserver",
|
"mockwebserver",
|
||||||
"net-utils-framework-common",
|
"net-utils-framework-common",
|
||||||
"truth-prebuilt",
|
"truth-prebuilt",
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package android.net.cts
|
|||||||
import android.Manifest.permission.CONNECTIVITY_INTERNAL
|
import android.Manifest.permission.CONNECTIVITY_INTERNAL
|
||||||
import android.Manifest.permission.NETWORK_SETTINGS
|
import android.Manifest.permission.NETWORK_SETTINGS
|
||||||
import android.Manifest.permission.READ_DEVICE_CONFIG
|
import android.Manifest.permission.READ_DEVICE_CONFIG
|
||||||
import android.Manifest.permission.WRITE_DEVICE_CONFIG
|
|
||||||
import android.content.pm.PackageManager.FEATURE_TELEPHONY
|
import android.content.pm.PackageManager.FEATURE_TELEPHONY
|
||||||
import android.content.pm.PackageManager.FEATURE_WIFI
|
import android.content.pm.PackageManager.FEATURE_WIFI
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
@@ -30,20 +29,25 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
|
|||||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.net.cts.NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig
|
||||||
|
import android.net.cts.NetworkValidationTestUtil.runAsShell
|
||||||
|
import android.net.cts.NetworkValidationTestUtil.setHttpUrlDeviceConfig
|
||||||
|
import android.net.cts.NetworkValidationTestUtil.setHttpsUrlDeviceConfig
|
||||||
|
import android.net.cts.NetworkValidationTestUtil.setUrlExpirationDeviceConfig
|
||||||
|
import com.android.testutils.TestHttpServer.Request
|
||||||
import android.net.cts.util.CtsNetUtils
|
import android.net.cts.util.CtsNetUtils
|
||||||
|
import android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
|
||||||
|
import android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ConditionVariable
|
|
||||||
import android.platform.test.annotations.AppModeFull
|
import android.platform.test.annotations.AppModeFull
|
||||||
import android.provider.DeviceConfig
|
import android.provider.DeviceConfig
|
||||||
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
|
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import com.android.compatibility.common.util.SystemUtil
|
import com.android.testutils.TestHttpServer
|
||||||
import com.android.testutils.isDevSdkInRange
|
import com.android.testutils.isDevSdkInRange
|
||||||
import fi.iki.elonen.NanoHTTPD
|
|
||||||
import fi.iki.elonen.NanoHTTPD.Response.IStatus
|
|
||||||
import fi.iki.elonen.NanoHTTPD.Response.Status
|
import fi.iki.elonen.NanoHTTPD.Response.Status
|
||||||
import junit.framework.AssertionFailedError
|
import junit.framework.AssertionFailedError
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@@ -55,15 +59,12 @@ import java.util.concurrent.TimeUnit
|
|||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertNotEquals
|
import kotlin.test.assertNotEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
private const val TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING = "test_captive_portal_https_url"
|
private const val TEST_HTTPS_URL_PATH = "/https_path"
|
||||||
private const val TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING = "test_captive_portal_http_url"
|
private const val TEST_HTTP_URL_PATH = "/http_path"
|
||||||
private const val TEST_URL_EXPIRATION_TIME = "test_url_expiration_time"
|
private const val TEST_PORTAL_URL_PATH = "/portal_path"
|
||||||
|
|
||||||
private const val TEST_HTTPS_URL_PATH = "https_path"
|
|
||||||
private const val TEST_HTTP_URL_PATH = "http_path"
|
|
||||||
private const val TEST_PORTAL_URL_PATH = "portal_path"
|
|
||||||
|
|
||||||
private const val LOCALHOST_HOSTNAME = "localhost"
|
private const val LOCALHOST_HOSTNAME = "localhost"
|
||||||
|
|
||||||
@@ -88,24 +89,24 @@ class CaptivePortalTest {
|
|||||||
private val pm by lazy { context.packageManager }
|
private val pm by lazy { context.packageManager }
|
||||||
private val utils by lazy { CtsNetUtils(context) }
|
private val utils by lazy { CtsNetUtils(context) }
|
||||||
|
|
||||||
private val server = HttpServer()
|
private val server = TestHttpServer("localhost")
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
doAsShell(READ_DEVICE_CONFIG) {
|
runAsShell(READ_DEVICE_CONFIG) {
|
||||||
// Verify that the test URLs are not normally set on the device, but do not fail if the
|
// Verify that the test URLs are not normally set on the device, but do not fail if the
|
||||||
// test URLs are set to what this test uses (URLs on localhost), in case the test was
|
// test URLs are set to what this test uses (URLs on localhost), in case the test was
|
||||||
// interrupted manually and rerun.
|
// interrupted manually and rerun.
|
||||||
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING)
|
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL)
|
||||||
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING)
|
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL)
|
||||||
}
|
}
|
||||||
clearTestUrls()
|
clearValidationTestUrlsDeviceConfig()
|
||||||
server.start()
|
server.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
clearTestUrls()
|
clearValidationTestUrlsDeviceConfig()
|
||||||
if (pm.hasSystemFeature(FEATURE_WIFI)) {
|
if (pm.hasSystemFeature(FEATURE_WIFI)) {
|
||||||
reconnectWifi()
|
reconnectWifi()
|
||||||
}
|
}
|
||||||
@@ -118,12 +119,6 @@ class CaptivePortalTest {
|
|||||||
"$urlKey must not be set in production scenarios (current value: $url)")
|
"$urlKey must not be set in production scenarios (current value: $url)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearTestUrls() {
|
|
||||||
setHttpsUrl(null)
|
|
||||||
setHttpUrl(null)
|
|
||||||
setUrlExpiration(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCaptivePortalIsNotDefaultNetwork() {
|
fun testCaptivePortalIsNotDefaultNetwork() {
|
||||||
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
|
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
|
||||||
@@ -132,19 +127,15 @@ class CaptivePortalTest {
|
|||||||
utils.connectToCell()
|
utils.connectToCell()
|
||||||
|
|
||||||
// Have network validation use a local server that serves a HTTPS error / HTTP redirect
|
// Have network validation use a local server that serves a HTTPS error / HTTP redirect
|
||||||
server.addResponse(TEST_PORTAL_URL_PATH, Status.OK,
|
server.addResponse(Request(TEST_PORTAL_URL_PATH), Status.OK,
|
||||||
content = "Test captive portal content")
|
content = "Test captive portal content")
|
||||||
server.addResponse(TEST_HTTPS_URL_PATH, Status.INTERNAL_ERROR)
|
server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
|
||||||
server.addResponse(TEST_HTTP_URL_PATH, Status.REDIRECT,
|
server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT,
|
||||||
locationHeader = server.makeUrl(TEST_PORTAL_URL_PATH))
|
locationHeader = makeUrl(TEST_PORTAL_URL_PATH))
|
||||||
setHttpsUrl(server.makeUrl(TEST_HTTPS_URL_PATH))
|
setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
|
||||||
setHttpUrl(server.makeUrl(TEST_HTTP_URL_PATH))
|
setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
|
||||||
// URL expiration needs to be in the next 10 minutes
|
// URL expiration needs to be in the next 10 minutes
|
||||||
setUrlExpiration(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(9))
|
setUrlExpirationDeviceConfig(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(9))
|
||||||
|
|
||||||
// Expect the portal content to be fetched at some point after detecting the portal.
|
|
||||||
// Some implementations may fetch the URL before startCaptivePortalApp is called.
|
|
||||||
val portalContentRequestCv = server.addExpectRequestCv(TEST_PORTAL_URL_PATH)
|
|
||||||
|
|
||||||
// Wait for a captive portal to be detected on the network
|
// Wait for a captive portal to be detected on the network
|
||||||
val wifiNetworkFuture = CompletableFuture<Network>()
|
val wifiNetworkFuture = CompletableFuture<Network>()
|
||||||
@@ -173,9 +164,14 @@ class CaptivePortalTest {
|
|||||||
val startPortalAppPermission =
|
val startPortalAppPermission =
|
||||||
if (isDevSdkInRange(0, Build.VERSION_CODES.Q)) CONNECTIVITY_INTERNAL
|
if (isDevSdkInRange(0, Build.VERSION_CODES.Q)) CONNECTIVITY_INTERNAL
|
||||||
else NETWORK_SETTINGS
|
else NETWORK_SETTINGS
|
||||||
doAsShell(startPortalAppPermission) { cm.startCaptivePortalApp(network) }
|
runAsShell(startPortalAppPermission) { cm.startCaptivePortalApp(network) }
|
||||||
assertTrue(portalContentRequestCv.block(TEST_TIMEOUT_MS), "The captive portal login " +
|
|
||||||
"page was still not fetched ${TEST_TIMEOUT_MS}ms after startCaptivePortalApp.")
|
// Expect the portal content to be fetched at some point after detecting the portal.
|
||||||
|
// Some implementations may fetch the URL before startCaptivePortalApp is called.
|
||||||
|
assertNotNull(server.requestsRecord.poll(TEST_TIMEOUT_MS, pos = 0) {
|
||||||
|
it.path == TEST_PORTAL_URL_PATH
|
||||||
|
}, "The captive portal login page was still not fetched ${TEST_TIMEOUT_MS}ms " +
|
||||||
|
"after startCaptivePortalApp.")
|
||||||
|
|
||||||
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
|
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -186,73 +182,13 @@ class CaptivePortalTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setHttpsUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING, url)
|
/**
|
||||||
private fun setHttpUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING, url)
|
* Create a URL string that, when fetched, will hit the test server with the given URL [path].
|
||||||
private fun setUrlExpiration(timestamp: Long?) = setConfig(TEST_URL_EXPIRATION_TIME,
|
*/
|
||||||
timestamp?.toString())
|
private fun makeUrl(path: String) = "http://localhost:${server.listeningPort}" + path
|
||||||
|
|
||||||
private fun setConfig(configKey: String, value: String?) {
|
|
||||||
doAsShell(WRITE_DEVICE_CONFIG) {
|
|
||||||
DeviceConfig.setProperty(
|
|
||||||
NAMESPACE_CONNECTIVITY, configKey, value, false /* makeDefault */)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doAsShell(vararg permissions: String, action: () -> Unit) {
|
|
||||||
// Wrap the below call to allow for more kotlin-like syntax
|
|
||||||
SystemUtil.runWithShellPermissionIdentity(action, permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reconnectWifi() {
|
private fun reconnectWifi() {
|
||||||
utils.ensureWifiDisconnected(null /* wifiNetworkToCheck */)
|
utils.ensureWifiDisconnected(null /* wifiNetworkToCheck */)
|
||||||
utils.ensureWifiConnected()
|
utils.ensureWifiConnected()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A minimal HTTP server running on localhost (loopback), on a random available port.
|
|
||||||
*/
|
|
||||||
private class HttpServer : NanoHTTPD("localhost", 0 /* auto-select the port */) {
|
|
||||||
// Map of URL path -> HTTP response code
|
|
||||||
private val responses = HashMap<String, Response>()
|
|
||||||
|
|
||||||
// Map of path -> CV to open as soon as a request to the path is received
|
|
||||||
private val waitForRequestCv = HashMap<String, ConditionVariable>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a URL string that, when fetched, will hit this server with the given URL [path].
|
|
||||||
*/
|
|
||||||
fun makeUrl(path: String): String {
|
|
||||||
return Uri.Builder()
|
|
||||||
.scheme("http")
|
|
||||||
.encodedAuthority("localhost:$listeningPort")
|
|
||||||
.query(path)
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addResponse(
|
|
||||||
path: String,
|
|
||||||
statusCode: IStatus,
|
|
||||||
locationHeader: String? = null,
|
|
||||||
content: String = ""
|
|
||||||
) {
|
|
||||||
val response = newFixedLengthResponse(statusCode, "text/plain", content)
|
|
||||||
locationHeader?.let { response.addHeader("Location", it) }
|
|
||||||
responses[path] = response
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [ConditionVariable] that will open when a request to [path] is received.
|
|
||||||
*/
|
|
||||||
fun addExpectRequestCv(path: String): ConditionVariable {
|
|
||||||
return ConditionVariable().apply { waitForRequestCv[path] = this }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serve(session: IHTTPSession): Response {
|
|
||||||
waitForRequestCv[session.queryParameterString]?.open()
|
|
||||||
return responses[session.queryParameterString]
|
|
||||||
// Default response is a 404
|
|
||||||
?: super.serve(session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ import android.net.NetworkRequest
|
|||||||
import android.net.TestNetworkInterface
|
import android.net.TestNetworkInterface
|
||||||
import android.net.TestNetworkManager
|
import android.net.TestNetworkManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.net.cts.NetworkValidationTestUtil.runAsShell
|
||||||
import android.net.dhcp.DhcpDiscoverPacket
|
import android.net.dhcp.DhcpDiscoverPacket
|
||||||
import android.net.dhcp.DhcpPacket
|
import android.net.dhcp.DhcpPacket
|
||||||
import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE
|
import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE
|
||||||
@@ -40,18 +41,18 @@ import android.os.HandlerThread
|
|||||||
import android.platform.test.annotations.AppModeFull
|
import android.platform.test.annotations.AppModeFull
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
|
|
||||||
import com.android.compatibility.common.util.ThrowingRunnable
|
|
||||||
import com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress
|
import com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress
|
||||||
import com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address
|
import com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address
|
||||||
import com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY
|
import com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY
|
||||||
|
import com.android.testutils.ArpResponder
|
||||||
import com.android.testutils.DevSdkIgnoreRule
|
import com.android.testutils.DevSdkIgnoreRule
|
||||||
import com.android.testutils.DhcpClientPacketFilter
|
import com.android.testutils.DhcpClientPacketFilter
|
||||||
import com.android.testutils.DhcpOptionFilter
|
import com.android.testutils.DhcpOptionFilter
|
||||||
import com.android.testutils.RecorderCallback.CallbackEntry
|
import com.android.testutils.RecorderCallback.CallbackEntry
|
||||||
import com.android.testutils.TapPacketReader
|
import com.android.testutils.TapPacketReader
|
||||||
|
import com.android.testutils.TestHttpServer
|
||||||
import com.android.testutils.TestableNetworkCallback
|
import com.android.testutils.TestableNetworkCallback
|
||||||
import fi.iki.elonen.NanoHTTPD
|
import fi.iki.elonen.NanoHTTPD.Response.Status
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assume.assumeFalse
|
import org.junit.Assume.assumeFalse
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -59,8 +60,6 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.net.Inet4Address
|
import java.net.Inet4Address
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@@ -79,7 +78,7 @@ private const val TEST_MTU = 1500.toShort()
|
|||||||
|
|
||||||
@AppModeFull(reason = "Instant apps cannot create test networks")
|
@AppModeFull(reason = "Instant apps cannot create test networks")
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class CaptivePortalApiTest {
|
class NetworkValidationTest {
|
||||||
@JvmField
|
@JvmField
|
||||||
@Rule
|
@Rule
|
||||||
val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
|
val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
|
||||||
@@ -89,10 +88,10 @@ class CaptivePortalApiTest {
|
|||||||
private val eth by lazy { context.assertHasService(EthernetManager::class.java) }
|
private val eth by lazy { context.assertHasService(EthernetManager::class.java) }
|
||||||
private val cm by lazy { context.assertHasService(ConnectivityManager::class.java) }
|
private val cm by lazy { context.assertHasService(ConnectivityManager::class.java) }
|
||||||
|
|
||||||
private val handlerThread = HandlerThread(CaptivePortalApiTest::class.java.simpleName)
|
private val handlerThread = HandlerThread(NetworkValidationTest::class.java.simpleName)
|
||||||
private val serverIpAddr = InetAddresses.parseNumericAddress("192.0.2.222") as Inet4Address
|
private val serverIpAddr = InetAddresses.parseNumericAddress("192.0.2.222") as Inet4Address
|
||||||
private val clientIpAddr = InetAddresses.parseNumericAddress("192.0.2.111") as Inet4Address
|
private val clientIpAddr = InetAddresses.parseNumericAddress("192.0.2.111") as Inet4Address
|
||||||
private val httpServer = HttpServer()
|
private val httpServer = TestHttpServer()
|
||||||
private val ethRequest = NetworkRequest.Builder()
|
private val ethRequest = NetworkRequest.Builder()
|
||||||
// ETHERNET|TEST transport networks do not have NET_CAPABILITY_TRUSTED
|
// ETHERNET|TEST transport networks do not have NET_CAPABILITY_TRUSTED
|
||||||
.removeCapability(NET_CAPABILITY_TRUSTED)
|
.removeCapability(NET_CAPABILITY_TRUSTED)
|
||||||
@@ -151,7 +150,15 @@ class CaptivePortalApiTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testApiCallbacks() {
|
fun testCapportApiCallbacks() {
|
||||||
|
httpServer.addResponse(capportUrl, Status.OK, content = """
|
||||||
|
|{
|
||||||
|
| "captive": true,
|
||||||
|
| "user-portal-url": "$TEST_LOGIN_URL",
|
||||||
|
| "venue-info-url": "$TEST_VENUE_INFO_URL"
|
||||||
|
|}
|
||||||
|
""".trimMargin())
|
||||||
|
|
||||||
// Handle the DHCP handshake that includes the capport API URL
|
// Handle the DHCP handshake that includes the capport API URL
|
||||||
val discover = reader.assertDhcpPacketReceived(
|
val discover = reader.assertDhcpPacketReceived(
|
||||||
DhcpDiscoverPacket::class.java, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_DISCOVER)
|
DhcpDiscoverPacket::class.java, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_DISCOVER)
|
||||||
@@ -163,11 +170,9 @@ class CaptivePortalApiTest {
|
|||||||
assertEquals(clientIpAddr, request.mRequestedIp)
|
assertEquals(clientIpAddr, request.mRequestedIp)
|
||||||
reader.sendResponse(makeAckPacket(request.clientMac, request.transactionId))
|
reader.sendResponse(makeAckPacket(request.clientMac, request.transactionId))
|
||||||
|
|
||||||
// Expect a request to the capport API
|
// The first request received by the server should be for the portal API
|
||||||
val capportReq = httpServer.recordedRequests.poll(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
assertTrue(httpServer.requestsRecord.poll(TEST_TIMEOUT_MS, 0)?.matches(capportUrl) ?: false,
|
||||||
assertNotNull(capportReq, "The device did not fetch captive portal API data within timeout")
|
"The device did not fetch captive portal API data within timeout")
|
||||||
assertEquals(capportUrl.path, capportReq.uri)
|
|
||||||
assertEquals(capportUrl.query, capportReq.queryParameterString)
|
|
||||||
|
|
||||||
// Expect network callbacks with capport info
|
// Expect network callbacks with capport info
|
||||||
val testCb = TestableNetworkCallback(TEST_TIMEOUT_MS)
|
val testCb = TestableNetworkCallback(TEST_TIMEOUT_MS)
|
||||||
@@ -216,30 +221,6 @@ class CaptivePortalApiTest {
|
|||||||
listOf(serverIpAddr) /* gateways */, listOf(serverIpAddr) /* dnsServers */,
|
listOf(serverIpAddr) /* gateways */, listOf(serverIpAddr) /* dnsServers */,
|
||||||
serverIpAddr, TEST_DOMAIN_NAME, null /* hostname */, true /* metered */,
|
serverIpAddr, TEST_DOMAIN_NAME, null /* hostname */, true /* metered */,
|
||||||
TEST_MTU, false /* rapidCommit */, capportUrl.toString())
|
TEST_MTU, false /* rapidCommit */, capportUrl.toString())
|
||||||
|
|
||||||
private fun parseDhcpPacket(bytes: ByteArray) = DhcpPacket.decodeFullPacket(
|
|
||||||
bytes, MAX_PACKET_LENGTH, DhcpPacket.ENCAP_L2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A minimal HTTP server running on localhost (loopback), on a random available port.
|
|
||||||
*
|
|
||||||
* The server records each request in [recordedRequests] and will not serve any further request
|
|
||||||
* until the last one is removed from the queue for verification.
|
|
||||||
*/
|
|
||||||
private class HttpServer : NanoHTTPD("localhost", 0 /* auto-select the port */) {
|
|
||||||
val recordedRequests = ArrayBlockingQueue<IHTTPSession>(1 /* capacity */)
|
|
||||||
|
|
||||||
override fun serve(session: IHTTPSession): Response {
|
|
||||||
recordedRequests.offer(session)
|
|
||||||
return newFixedLengthResponse("""
|
|
||||||
|{
|
|
||||||
| "captive": true,
|
|
||||||
| "user-portal-url": "$TEST_LOGIN_URL",
|
|
||||||
| "venue-info-url": "$TEST_VENUE_INFO_URL"
|
|
||||||
|}
|
|
||||||
""".trimMargin())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : DhcpPacket> TapPacketReader.assertDhcpPacketReceived(
|
private fun <T : DhcpPacket> TapPacketReader.assertDhcpPacketReceived(
|
||||||
@@ -259,12 +240,3 @@ private fun <T : DhcpPacket> TapPacketReader.assertDhcpPacketReceived(
|
|||||||
private fun <T> Context.assertHasService(manager: Class<T>): T {
|
private fun <T> Context.assertHasService(manager: Class<T>): T {
|
||||||
return getSystemService(manager) ?: fail("Service $manager not found")
|
return getSystemService(manager) ?: fail("Service $manager not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper around runWithShellPermissionIdentity with kotlin-like syntax.
|
|
||||||
*/
|
|
||||||
private fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
|
|
||||||
var ret: T? = null
|
|
||||||
runWithShellPermissionIdentity(ThrowingRunnable { ret = task() }, *permissions)
|
|
||||||
return ret ?: fail("ThrowingRunnable was not run")
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.net.cts
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.net.util.NetworkStackUtils
|
||||||
|
import android.provider.DeviceConfig
|
||||||
|
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
|
||||||
|
import com.android.compatibility.common.util.ThrowingRunnable
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of utility methods for configuring network validation.
|
||||||
|
*/
|
||||||
|
internal object NetworkValidationTestUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the test network validation URLs.
|
||||||
|
*/
|
||||||
|
fun clearValidationTestUrlsDeviceConfig() {
|
||||||
|
setHttpsUrlDeviceConfig(null)
|
||||||
|
setHttpUrlDeviceConfig(null)
|
||||||
|
setUrlExpirationDeviceConfig(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the test validation HTTPS URL.
|
||||||
|
*
|
||||||
|
* @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
|
||||||
|
*/
|
||||||
|
fun setHttpsUrlDeviceConfig(url: String?) =
|
||||||
|
setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL, url)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the test validation HTTP URL.
|
||||||
|
*
|
||||||
|
* @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
|
||||||
|
*/
|
||||||
|
fun setHttpUrlDeviceConfig(url: String?) =
|
||||||
|
setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL, url)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the test validation URL expiration.
|
||||||
|
*
|
||||||
|
* @see NetworkStackUtils.TEST_URL_EXPIRATION_TIME
|
||||||
|
*/
|
||||||
|
fun setUrlExpirationDeviceConfig(timestamp: Long?) =
|
||||||
|
setConfig(NetworkStackUtils.TEST_URL_EXPIRATION_TIME, timestamp?.toString())
|
||||||
|
|
||||||
|
private fun setConfig(configKey: String, value: String?) {
|
||||||
|
runAsShell(Manifest.permission.WRITE_DEVICE_CONFIG) {
|
||||||
|
DeviceConfig.setProperty(
|
||||||
|
DeviceConfig.NAMESPACE_CONNECTIVITY, configKey, value, false /* makeDefault */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around runWithShellPermissionIdentity with kotlin-like syntax.
|
||||||
|
*/
|
||||||
|
fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
|
||||||
|
var ret: T? = null
|
||||||
|
runWithShellPermissionIdentity(ThrowingRunnable { ret = task() }, *permissions)
|
||||||
|
return ret ?: fail("ThrowingRunnable did not return")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user