From 74a6ca6478245bb40d1b447cca41f3cb279dee04 Mon Sep 17 00:00:00 2001 From: Kangping Dong Date: Wed, 12 Mar 2025 17:50:22 +0800 Subject: [PATCH] [Thread] prefer only ML-EID in case OMR is missing This is to avoid applications uses Aloc address for source address which would cause issues when there are multiple devices sharing the same Aloc. Bug: 396019291 Change-Id: I287007ddecd9d0fa2ac8da92f4de691e7844b259 --- .../server/thread/TunInterfaceController.java | 7 +- .../thread/cts/ThreadNetworkManagerTest.java | 4 - .../net/thread/ThreadIntegrationTest.java | 85 ++++++++++++++++--- .../net/thread/utils/IntegrationTestUtils.kt | 6 ++ .../net/thread/utils/OtDaemonController.java | 21 +++++ 5 files changed, 102 insertions(+), 21 deletions(-) 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");