[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
This commit is contained in:
Kangping Dong 2025-03-12 17:50:22 +08:00
commit 74a6ca6478
5 changed files with 102 additions and 21 deletions

View file

@ -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 =

View file

@ -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)

View file

@ -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<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

View file

@ -479,6 +479,12 @@ object IntegrationTestUtils {
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)

View file

@ -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<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");