diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java index 520a434747..2f2a5d1c28 100644 --- a/thread/service/java/com/android/server/thread/TunInterfaceController.java +++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java @@ -326,12 +326,13 @@ public class TunInterfaceController { private static LinkAddress newLinkAddress( Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress) { // Mesh-local addresses and OMR address have the same scope, to distinguish them we set - // mesh-local addresses as deprecated when there is an active OMR address. + // mesh-local addresses as deprecated when there is an active OMR address. If OMR address + // is missing, only ML-EID in mesh-local addresses will be set preferred. // For OMR address and link-local address we only use the value isPreferred set by // ot-daemon. boolean isPreferred = addressInfo.isPreferred; - if (addressInfo.isMeshLocal && hasActiveOmrAddress) { - isPreferred = false; + if (addressInfo.isMeshLocal) { + isPreferred = (!hasActiveOmrAddress && addressInfo.isMeshLocalEid); } final long deprecationTimeMillis = diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java index 5ba76b8d09..5be8f4930b 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java @@ -19,12 +19,10 @@ package android.net.thread.cts; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeNotNull; import static org.junit.Assume.assumeTrue; import android.content.Context; import android.content.pm.PackageManager; -import android.net.thread.ThreadNetworkController; import android.net.thread.ThreadNetworkManager; import android.os.Build; @@ -41,8 +39,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.List; - /** Tests for {@link ThreadNetworkManager}. */ @SmallTest @RunWith(AndroidJUnit4.class) diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java index 195f6d26de..5b07e0af38 100644 --- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java +++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java @@ -22,12 +22,14 @@ import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER; import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED; import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT; import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT; +import static android.net.thread.utils.IntegrationTestUtils.getIpv6Addresses; import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses; import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData; import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork; import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup; import static android.net.thread.utils.IntegrationTestUtils.waitFor; import static android.net.thread.utils.ThreadNetworkControllerWrapper.JOIN_TIMEOUT; +import static android.os.SystemClock.elapsedRealtime; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; @@ -38,6 +40,7 @@ import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import static java.util.concurrent.TimeUnit.SECONDS; @@ -46,23 +49,26 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.thread.utils.FullThreadDevice; import android.net.thread.utils.OtDaemonController; +import android.net.thread.utils.TapTestNetworkTracker; import android.net.thread.utils.ThreadFeatureCheckerRule; import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice; import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature; import android.net.thread.utils.ThreadNetworkControllerWrapper; import android.net.thread.utils.ThreadStateListener; +import android.os.HandlerThread; import android.os.SystemClock; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.LargeTest; +import com.google.common.collect.FluentIterable; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -102,6 +108,12 @@ public class ThreadIntegrationTest { private static final Duration NETWORK_CALLBACK_TIMEOUT = Duration.ofSeconds(10); + // The duration between attached and OMR address shows up on thread-wpan + private static final Duration OMR_LINK_ADDR_TIMEOUT = Duration.ofSeconds(30); + + // The duration between attached and addresses show up on thread-wpan + private static final Duration LINK_ADDR_TIMEOUT = Duration.ofSeconds(2); + // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new". private static final byte[] DEFAULT_DATASET_TLVS = base16().decode( @@ -128,6 +140,8 @@ public class ThreadIntegrationTest { ThreadNetworkControllerWrapper.newInstance(mContext); private OtDaemonController mOtCtl; private FullThreadDevice mFtd; + private HandlerThread mHandlerThread; + private TapTestNetworkTracker mTestNetworkTracker; public final boolean mIsBorderRouterEnabled; private final ThreadConfiguration mConfig; @@ -152,6 +166,14 @@ public class ThreadIntegrationTest { mController.setEnabledAndWait(true); mController.setConfigurationAndWait(mConfig); mController.leaveAndWait(); + + mHandlerThread = new HandlerThread("ThreadIntegrationTest"); + mHandlerThread.start(); + + mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper()); + assertThat(mTestNetworkTracker).isNotNull(); + mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName()); + mFtd = new FullThreadDevice(10 /* nodeId */); } @@ -159,6 +181,13 @@ public class ThreadIntegrationTest { public void tearDown() throws Exception { ThreadStateListener.stopAllListeners(); + if (mTestNetworkTracker != null) { + mTestNetworkTracker.tearDown(); + } + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread.join(); + } mController.setTestNetworkAsUpstreamAndWait(null); mController.leaveAndWait(); @@ -265,23 +294,51 @@ public class ThreadIntegrationTest { } @Test - public void joinNetworkWithBrDisabled_meshLocalAddressesArePreferred() throws Exception { - // When BR feature is disabled, there is no OMR address, so the mesh-local addresses are - // expected to be preferred. - mOtCtl.executeCommand("br disable"); + public void joinNetwork_borderRouterEnabled_allMlAddrAreNotPreferredAndOmrIsPreferred() + throws Exception { + assumeTrue(mConfig.isBorderRouterEnabled()); + + mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName()); mController.joinAndWait(DEFAULT_DATASET); + waitFor( + () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getOmrAddress()), + OMR_LINK_ADDR_TIMEOUT); IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix(); - List linkAddresses = getIpv6LinkAddresses("thread-wpan"); - for (LinkAddress address : linkAddresses) { - if (meshLocalPrefix.contains(address.getAddress())) { - assertThat(address.getDeprecationTime()) - .isGreaterThan(SystemClock.elapsedRealtime()); - assertThat(address.isPreferred()).isTrue(); - } - } + var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan")); + var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress())); + assertThat(meshLocalAddrs).isNotEmpty(); + assertThat(meshLocalAddrs.allMatch(addr -> !addr.isPreferred())).isTrue(); + assertThat(meshLocalAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime())) + .isTrue(); + var omrAddrs = linkAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getOmrAddress())); + assertThat(omrAddrs).hasSize(1); + assertThat(omrAddrs.get(0).isPreferred()).isTrue(); + assertThat(omrAddrs.get(0).getDeprecationTime() > elapsedRealtime()).isTrue(); + } - mOtCtl.executeCommand("br enable"); + @Test + public void joinNetwork_borderRouterDisabled_onlyMlEidIsPreferred() throws Exception { + assumeFalse(mConfig.isBorderRouterEnabled()); + + mController.joinAndWait(DEFAULT_DATASET); + waitFor( + () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getMlEid()), + LINK_ADDR_TIMEOUT); + + IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix(); + var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan")); + var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress())); + var mlEidAddrs = meshLocalAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getMlEid())); + var nonMlEidAddrs = meshLocalAddrs.filter(addr -> !mlEidAddrs.contains(addr)); + assertThat(mlEidAddrs).hasSize(1); + assertThat(mlEidAddrs.allMatch(addr -> addr.isPreferred())).isTrue(); + assertThat(mlEidAddrs.allMatch(addr -> addr.getDeprecationTime() > elapsedRealtime())) + .isTrue(); + assertThat(nonMlEidAddrs).isNotEmpty(); + assertThat(nonMlEidAddrs.allMatch(addr -> !addr.isPreferred())).isTrue(); + assertThat(nonMlEidAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime())) + .isTrue(); } @Test diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt index f41e903e1d..f7b4d19073 100644 --- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt +++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt @@ -479,6 +479,12 @@ object IntegrationTestUtils { return addresses } + /** Returns the list of [InetAddress] of the given network. */ + @JvmStatic + fun getIpv6Addresses(interfaceName: String): List { + return getIpv6LinkAddresses(interfaceName).map { it.address } + } + /** Return the first discovered service of `serviceType`. */ @JvmStatic @Throws(Exception::class) diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java index 272685fa4c..d35b94e72f 100644 --- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java +++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java @@ -24,9 +24,11 @@ import android.os.SystemClock; import com.android.compatibility.common.util.SystemUtil; import java.net.Inet6Address; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to @@ -72,6 +74,25 @@ public final class OtDaemonController { .toList(); } + /** Returns the OMR address of this device or {@code null} if it doesn't exist. */ + @Nullable + public Inet6Address getOmrAddress() { + List allAddresses = new ArrayList<>(getAddresses()); + allAddresses.removeAll(getMeshLocalAddresses()); + + List omrAddresses = + allAddresses.stream() + .filter(addr -> !addr.isLinkLocalAddress()) + .collect(Collectors.toList()); + if (omrAddresses.isEmpty()) { + return null; + } else if (omrAddresses.size() > 1) { + throw new IllegalStateException(); + } + + return omrAddresses.getFirst(); + } + /** Returns {@code true} if the Thread interface is up. */ public boolean isInterfaceUp() { String output = executeCommand("ifconfig");