[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
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 520a434..2f2a5d1 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 @@
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 5ba76b8..5be8f49 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 @@
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.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 195f6d2..5b07e0a 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_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.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.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 @@
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 @@
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 @@
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 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 @@
}
@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<LinkAddress> 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 f41e903..f7b4d19 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 @@
return addresses
}
+ /** Returns the list of [InetAddress] of the given network. */
+ @JvmStatic
+ fun getIpv6Addresses(interfaceName: String): List<InetAddress> {
+ 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 272685f..d35b94e 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 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 @@
.toList();
}
+ /** Returns the OMR address of this device or {@code null} if it doesn't exist. */
+ @Nullable
+ public Inet6Address getOmrAddress() {
+ List<Inet6Address> allAddresses = new ArrayList<>(getAddresses());
+ allAddresses.removeAll(getMeshLocalAddresses());
+
+ List<Inet6Address> 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");