summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHanda Wang <handaw@google.com>2024-02-28 08:27:18 +0000
committerHanda Wang <handaw@google.com>2024-02-28 14:30:22 +0000
commit55648268de65a2e26dfd6a77d34ae18404a05d94 (patch)
treebeb585f5b77612c33b82889f893c75e15af1862c
parentaa26b685f761486f1720b448349e53fad8f781de (diff)
downloadConnectivity-55648268de65a2e26dfd6a77d34ae18404a05d94.tar.gz
Use a TAP test network for MeshCoP service test cases
Previously we were using TUN test network which is not eligible for mDNS operations. The tests are flaky because we need to depend on other test cases to connect to WiFi, which is not guaranteed. We'll use the TAP test network to fix it. Bug: 327306555 Bug: 327306158 Bug: 327306608 Change-Id: Ia6de8a507578dbdeea4a7ceaf52f417acf35baa2
-rw-r--r--thread/tests/cts/Android.bp1
-rw-r--r--thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java47
-rw-r--r--thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java1
-rw-r--r--thread/tests/utils/Android.bp37
-rw-r--r--thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java185
5 files changed, 248 insertions, 23 deletions
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 676eb0e696..c1cf0a09ee 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -42,6 +42,7 @@ android_test {
"guava",
"guava-android-testlib",
"net-tests-utils",
+ "ThreadNetworkTestUtils",
"truth",
],
libs: [
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 3bec36b46b..95496568ca 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -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,6 +121,8 @@ public class ThreadNetworkControllerTest {
private NsdManager mNsdManager;
private Set<String> mGrantedPermissions;
+ private HandlerThread mHandlerThread;
+ private TapTestNetworkTracker mTestNetworkTracker;
@Before
public void setUp() throws Exception {
@@ -141,6 +141,8 @@ public class ThreadNetworkControllerTest {
setEnabledAndWait(mController, true);
mNsdManager = mContext.getSystemService(NsdManager.class);
+ mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+ mHandlerThread.start();
}
@After
@@ -152,6 +154,7 @@ public class ThreadNetworkControllerTest {
future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
}
dropAllPermissions();
+ tearDownTestNetwork();
}
@Test
@@ -829,7 +832,7 @@ public class ThreadNetworkControllerTest {
@Test
public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
- TestNetworkTracker testNetwork = setUpTestNetwork();
+ setUpTestNetwork();
setEnabledAndWait(mController, true);
leaveAndWait(mController);
@@ -845,13 +848,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 +873,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 +1163,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 {
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 4948c22c3d..60a5f2b618 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -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;
diff --git a/thread/tests/utils/Android.bp b/thread/tests/utils/Android.bp
new file mode 100644
index 0000000000..24e9bb9c8f
--- /dev/null
+++ b/thread/tests/utils/Android.bp
@@ -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",
+ ],
+}
diff --git a/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
new file mode 100644
index 0000000000..43f177df06
--- /dev/null
+++ b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
@@ -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;
+ }
+}