Merge "[Thread] prefer only ML-EID in case OMR is missing" into main

This commit is contained in:
Treehugger Robot 2025-03-13 19:19:42 -07:00 • committed by Gerrit Code Review
commit 93fa683874
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");