Merge "Use a TAP test network for MeshCoP service test cases" into main
This commit is contained in:
commit
2b071e919b
5 changed files with 255 additions and 33 deletions
|
|
@ -42,6 +42,7 @@ android_test {
|
|||
"guava",
|
||||
"guava-android-testlib",
|
||||
"net-tests-utils",
|
||||
"ThreadNetworkTestUtils",
|
||||
"truth",
|
||||
],
|
||||
libs: [
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package android.net.thread.cts;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
|
||||
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
|
||||
|
|
@ -33,7 +32,6 @@ import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
|
|||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
|
@ -48,7 +46,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
|
|
@ -62,7 +59,9 @@ import android.net.thread.ThreadNetworkController.OperationalDatasetCallback;
|
|||
import android.net.thread.ThreadNetworkController.StateCallback;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.net.thread.ThreadNetworkManager;
|
||||
import android.net.thread.utils.TapTestNetworkTracker;
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.OutcomeReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -74,7 +73,6 @@ import com.android.testutils.DevSdkIgnoreRule;
|
|||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
|
||||
import com.android.testutils.TestNetworkTracker;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
|
@ -110,7 +108,7 @@ public class ThreadNetworkControllerTest {
|
|||
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
|
||||
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
|
||||
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
|
||||
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 10 * 1000;
|
||||
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
|
||||
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
|
||||
private static final String THREAD_NETWORK_PRIVILEGED =
|
||||
"android.permission.THREAD_NETWORK_PRIVILEGED";
|
||||
|
|
@ -123,12 +121,11 @@ public class ThreadNetworkControllerTest {
|
|||
private NsdManager mNsdManager;
|
||||
|
||||
private Set<String> mGrantedPermissions;
|
||||
private HandlerThread mHandlerThread;
|
||||
private TapTestNetworkTracker mTestNetworkTracker;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
mGrantedPermissions = new HashSet<String>();
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
|
||||
if (manager != null) {
|
||||
mController = manager.getAllThreadNetworkControllers().get(0);
|
||||
|
|
@ -138,20 +135,23 @@ public class ThreadNetworkControllerTest {
|
|||
// tests if a feature is not available.
|
||||
assumeNotNull(mController);
|
||||
|
||||
setEnabledAndWait(mController, true);
|
||||
|
||||
mGrantedPermissions = new HashSet<String>();
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mNsdManager = mContext.getSystemService(NsdManager.class);
|
||||
mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
|
||||
mHandlerThread.start();
|
||||
|
||||
setEnabledAndWait(mController, true);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (mController != null) {
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
mController.leave(mExecutor, future::complete);
|
||||
future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
if (mController == null) {
|
||||
return;
|
||||
}
|
||||
dropAllPermissions();
|
||||
leaveAndWait(mController);
|
||||
tearDownTestNetwork();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -829,7 +829,7 @@ public class ThreadNetworkControllerTest {
|
|||
|
||||
@Test
|
||||
public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
|
||||
TestNetworkTracker testNetwork = setUpTestNetwork();
|
||||
setUpTestNetwork();
|
||||
|
||||
setEnabledAndWait(mController, true);
|
||||
leaveAndWait(mController);
|
||||
|
|
@ -845,13 +845,11 @@ public class ThreadNetworkControllerTest {
|
|||
assertThat(txtMap.get("rv")).isNotNull();
|
||||
assertThat(txtMap.get("tv")).isNotNull();
|
||||
assertThat(txtMap.get("sb")).isNotNull();
|
||||
|
||||
tearDownTestNetwork(testNetwork);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
|
||||
TestNetworkTracker testNetwork = setUpTestNetwork();
|
||||
setUpTestNetwork();
|
||||
|
||||
String networkName = "TestNet" + new Random().nextInt(10_000);
|
||||
joinRandomizedDatasetAndWait(mController, networkName);
|
||||
|
|
@ -872,27 +870,26 @@ public class ThreadNetworkControllerTest {
|
|||
assertThat(txtMap.get("tv")).isNotNull();
|
||||
assertThat(txtMap.get("sb")).isNotNull();
|
||||
assertThat(txtMap.get("id").length).isEqualTo(16);
|
||||
|
||||
tearDownTestNetwork(testNetwork);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meshcopService_threadDisabled_notDiscovered() throws Exception {
|
||||
TestNetworkTracker testNetwork = setUpTestNetwork();
|
||||
setUpTestNetwork();
|
||||
|
||||
CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
|
||||
NsdManager.DiscoveryListener listener =
|
||||
discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
|
||||
setEnabledAndWait(mController, false);
|
||||
|
||||
try {
|
||||
serviceLostFuture.get(10_000, MILLISECONDS);
|
||||
serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException ignored) {
|
||||
// It's fine if the service lost event didn't show up. The service may not ever be
|
||||
// advertised.
|
||||
} finally {
|
||||
mNsdManager.stopServiceDiscovery(listener);
|
||||
}
|
||||
assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
|
||||
|
||||
tearDownTestNetwork(testNetwork);
|
||||
assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
|
||||
}
|
||||
|
||||
private static void dropAllPermissions() {
|
||||
|
|
@ -1163,14 +1160,17 @@ public class ThreadNetworkControllerTest {
|
|||
}
|
||||
}
|
||||
|
||||
TestNetworkTracker setUpTestNetwork() {
|
||||
return runAsShell(
|
||||
MANAGE_TEST_NETWORKS,
|
||||
() -> initTestNetwork(mContext, new LinkAddress("2001:db8:123::/64"), 10_000));
|
||||
private void setUpTestNetwork() {
|
||||
assertThat(mTestNetworkTracker).isNull();
|
||||
mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
|
||||
}
|
||||
|
||||
void tearDownTestNetwork(TestNetworkTracker testNetwork) {
|
||||
runAsShell(MANAGE_TEST_NETWORKS, () -> testNetwork.teardown());
|
||||
private void tearDownTestNetwork() throws InterruptedException {
|
||||
if (mTestNetworkTracker != null) {
|
||||
mTestNetworkTracker.tearDown();
|
||||
}
|
||||
mHandlerThread.quitSafely();
|
||||
mHandlerThread.join();
|
||||
}
|
||||
|
||||
private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
|
||||
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
|
||||
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
|
||||
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
|
||||
|
|
|
|||
37
thread/tests/utils/Android.bp
Normal file
37
thread/tests/utils/Android.bp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// Copyright (C) 2023 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 {
|
||||
default_team: "trendy_team_fwk_thread_network",
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "ThreadNetworkTestUtils",
|
||||
min_sdk_version: "30",
|
||||
static_libs: [
|
||||
"compatibility-device-util-axt",
|
||||
"net-tests-utils",
|
||||
"net-utils-device-common",
|
||||
"net-utils-device-common-bpf",
|
||||
],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
defaults: [
|
||||
"framework-connectivity-test-defaults",
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright (C) 2024 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.thread.utils;
|
||||
|
||||
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
|
||||
import static android.net.InetAddresses.parseNumericAddress;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
import static android.system.OsConstants.SOCK_DGRAM;
|
||||
|
||||
import static com.android.testutils.RecorderCallback.CallbackEntry.LINK_PROPERTIES_CHANGED;
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.TestNetworkInterface;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.net.TestNetworkSpecifier;
|
||||
import android.os.Looper;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
|
||||
import com.android.compatibility.common.util.PollingCheck;
|
||||
import com.android.testutils.TestableNetworkAgent;
|
||||
import com.android.testutils.TestableNetworkCallback;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** A class that can create/destroy a test network based on TAP interface. */
|
||||
public final class TapTestNetworkTracker {
|
||||
private static final Duration TIMEOUT = Duration.ofSeconds(2);
|
||||
private final Context mContext;
|
||||
private final Looper mLooper;
|
||||
private TestNetworkInterface mInterface;
|
||||
private TestableNetworkAgent mAgent;
|
||||
private final TestableNetworkCallback mNetworkCallback;
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
|
||||
/**
|
||||
* Constructs a {@link TapTestNetworkTracker}.
|
||||
*
|
||||
* <p>It creates a TAP interface (e.g. testtap0) and registers a test network using that
|
||||
* interface. It also requests the test network by {@link ConnectivityManager#requestNetwork} so
|
||||
* the test network won't be automatically turned down by {@link
|
||||
* com.android.server.ConnectivityService}.
|
||||
*/
|
||||
public TapTestNetworkTracker(Context context, Looper looper) {
|
||||
mContext = context;
|
||||
mLooper = looper;
|
||||
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
||||
mNetworkCallback = new TestableNetworkCallback();
|
||||
runAsShell(MANAGE_TEST_NETWORKS, this::setUpTestNetwork);
|
||||
}
|
||||
|
||||
/** Tears down the test network. */
|
||||
public void tearDown() {
|
||||
runAsShell(MANAGE_TEST_NETWORKS, this::tearDownTestNetwork);
|
||||
}
|
||||
|
||||
/** Returns the interface name of the test network. */
|
||||
public String getInterfaceName() {
|
||||
return mInterface.getInterfaceName();
|
||||
}
|
||||
|
||||
private void setUpTestNetwork() throws Exception {
|
||||
mInterface = mContext.getSystemService(TestNetworkManager.class).createTapInterface();
|
||||
|
||||
mConnectivityManager.requestNetwork(newNetworkRequest(), mNetworkCallback);
|
||||
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName(getInterfaceName());
|
||||
mAgent =
|
||||
new TestableNetworkAgent(
|
||||
mContext,
|
||||
mLooper,
|
||||
newNetworkCapabilities(),
|
||||
lp,
|
||||
new NetworkAgentConfig.Builder().build());
|
||||
final Network network = mAgent.register();
|
||||
mAgent.markConnected();
|
||||
|
||||
PollingCheck.check(
|
||||
"No usable address on interface",
|
||||
TIMEOUT.toMillis(),
|
||||
() -> hasUsableAddress(network, getInterfaceName()));
|
||||
|
||||
lp.setLinkAddresses(makeLinkAddresses());
|
||||
mAgent.sendLinkProperties(lp);
|
||||
mNetworkCallback.eventuallyExpect(
|
||||
LINK_PROPERTIES_CHANGED,
|
||||
TIMEOUT.toMillis(),
|
||||
l -> !l.getLp().getAddresses().isEmpty());
|
||||
}
|
||||
|
||||
private void tearDownTestNetwork() throws IOException {
|
||||
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
|
||||
mAgent.unregister();
|
||||
mInterface.getFileDescriptor().close();
|
||||
mAgent.waitForIdle(TIMEOUT.toMillis());
|
||||
}
|
||||
|
||||
private NetworkRequest newNetworkRequest() {
|
||||
return new NetworkRequest.Builder()
|
||||
.removeCapability(NET_CAPABILITY_TRUSTED)
|
||||
.addTransportType(TRANSPORT_TEST)
|
||||
.setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private NetworkCapabilities newNetworkCapabilities() {
|
||||
return new NetworkCapabilities()
|
||||
.removeCapability(NET_CAPABILITY_TRUSTED)
|
||||
.addTransportType(TRANSPORT_TEST)
|
||||
.setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName()));
|
||||
}
|
||||
|
||||
private List<LinkAddress> makeLinkAddresses() {
|
||||
List<LinkAddress> linkAddresses = new ArrayList<>();
|
||||
List<InterfaceAddress> interfaceAddresses = Collections.emptyList();
|
||||
|
||||
try {
|
||||
interfaceAddresses =
|
||||
NetworkInterface.getByName(getInterfaceName()).getInterfaceAddresses();
|
||||
} catch (SocketException ignored) {
|
||||
// Ignore failures when getting the addresses.
|
||||
}
|
||||
|
||||
for (InterfaceAddress address : interfaceAddresses) {
|
||||
linkAddresses.add(
|
||||
new LinkAddress(address.getAddress(), address.getNetworkPrefixLength()));
|
||||
}
|
||||
|
||||
return linkAddresses;
|
||||
}
|
||||
|
||||
private static boolean hasUsableAddress(Network network, String interfaceName) {
|
||||
try {
|
||||
if (NetworkInterface.getByName(interfaceName).getInterfaceAddresses().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
return false;
|
||||
}
|
||||
// Check if the link-local address can be used. Address flags are not available without
|
||||
// elevated permissions, so check that bindSocket works.
|
||||
try {
|
||||
FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
||||
network.bindSocket(sock);
|
||||
Os.connect(sock, parseNumericAddress("ff02::fb%" + interfaceName), 12345);
|
||||
Os.close(sock);
|
||||
} catch (ErrnoException | IOException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue