Dynamically set mesh-local addresses preferred lifetime.
When there is an active OMR address(not deprecated), set the mesh-local
addresses as non-preferred. When there is no active OMR address, set
the mesh-local addresses as preferred.
Bug: 323624146
Test: atest ThreadNetworkIntegrationTests
Change-Id: I969c20ddadfd2d0bd97499da48efc9b32cfa7e0b
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 63cd574..7c757c0 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -73,7 +73,6 @@
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
-import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
import android.net.LocalNetworkInfo;
@@ -106,7 +105,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
@@ -129,8 +127,6 @@
import java.io.IOException;
import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
@@ -260,38 +256,6 @@
countryCodeSupplier);
}
- private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
- try {
- return (Inet6Address) Inet6Address.getByAddress(addressBytes);
- } catch (UnknownHostException e) {
- // This is unlikely to happen unless the Thread daemon is critically broken
- return null;
- }
- }
-
- private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
- return bytesToInet6Address(addressInfo.address);
- }
-
- private static LinkAddress newLinkAddress(Ipv6AddressInfo addressInfo) {
- long deprecationTimeMillis =
- addressInfo.isPreferred
- ? LinkAddress.LIFETIME_PERMANENT
- : SystemClock.elapsedRealtime();
-
- InetAddress address = addressInfoToInetAddress(addressInfo);
-
- // flags and scope will be adjusted automatically depending on the address and
- // its lifetimes.
- return new LinkAddress(
- address,
- addressInfo.prefixLength,
- 0 /* flags */,
- 0 /* scope */,
- deprecationTimeMillis,
- LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
- }
-
private NetworkRequest newUpstreamNetworkRequest() {
NetworkRequest.Builder builder = new NetworkRequest.Builder().clearCapabilities();
@@ -1172,20 +1136,10 @@
}
}
- private void handleAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
+ private void handleAddressChanged(List<Ipv6AddressInfo> addressInfoList) {
checkOnHandlerThread();
- InetAddress address = addressInfoToInetAddress(addressInfo);
- if (address.isMulticastAddress()) {
- Log.i(TAG, "Ignoring multicast address " + address.getHostAddress());
- return;
- }
- LinkAddress linkAddress = newLinkAddress(addressInfo);
- if (isAdded) {
- mTunIfController.addAddress(linkAddress);
- } else {
- mTunIfController.removeAddress(linkAddress);
- }
+ mTunIfController.updateAddresses(addressInfoList);
// The OT daemon can send link property updates before the networkAgent is
// registered
@@ -1534,8 +1488,8 @@
}
@Override
- public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
- mHandler.post(() -> handleAddressChanged(addressInfo, isAdded));
+ public void onAddressChanged(List<Ipv6AddressInfo> addressInfoList) {
+ mHandler.post(() -> handleAddressChanged(addressInfoList));
}
@Override
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index b29a54f..97cdd55 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -29,12 +29,19 @@
import android.system.OsConstants;
import android.util.Log;
+import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.netlink.NetlinkUtils;
import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.server.thread.openthread.Ipv6AddressInfo;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
/** Controller for virtual/tunnel network interfaces. */
public class TunInterfaceController {
@@ -178,6 +185,38 @@
}
}
+ public void updateAddresses(List<Ipv6AddressInfo> addressInfoList) {
+ final List<LinkAddress> newLinkAddresses = new ArrayList<>();
+ boolean hasActiveOmrAddress = false;
+
+ for (Ipv6AddressInfo addressInfo : addressInfoList) {
+ if (addressInfo.isActiveOmr) {
+ hasActiveOmrAddress = true;
+ break;
+ }
+ }
+
+ for (Ipv6AddressInfo addressInfo : addressInfoList) {
+ InetAddress address = addressInfoToInetAddress(addressInfo);
+ if (address.isMulticastAddress()) {
+ // TODO: Logging here will create repeated logs for a single multicast address, and
+ // it currently is not mandatory for debugging. Add log for ignored multicast
+ // address when necessary.
+ continue;
+ }
+ newLinkAddresses.add(newLinkAddress(addressInfo, hasActiveOmrAddress));
+ }
+
+ final CompareResult<LinkAddress> addressDiff =
+ new CompareResult<>(mLinkProperties.getAllLinkAddresses(), newLinkAddresses);
+ for (LinkAddress linkAddress : addressDiff.removed) {
+ removeAddress(linkAddress);
+ }
+ for (LinkAddress linkAddress : addressDiff.added) {
+ addAddress(linkAddress);
+ }
+ }
+
private RouteInfo getRouteForAddress(LinkAddress linkAddress) {
return new RouteInfo(
new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()),
@@ -195,4 +234,44 @@
Log.e(TAG, "Failed to set Thread TUN interface down");
}
}
+
+ private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
+ return bytesToInet6Address(addressInfo.address);
+ }
+
+ private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
+ try {
+ return (Inet6Address) Inet6Address.getByAddress(addressBytes);
+ } catch (UnknownHostException e) {
+ // This is unlikely to happen unless the Thread daemon is critically broken
+ return null;
+ }
+ }
+
+ 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.
+ // 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;
+ }
+
+ final long deprecationTimeMillis =
+ isPreferred ? LinkAddress.LIFETIME_PERMANENT : SystemClock.elapsedRealtime();
+
+ final InetAddress address = addressInfoToInetAddress(addressInfo);
+
+ // flags and scope will be adjusted automatically depending on the address and
+ // its lifetimes.
+ return new LinkAddress(
+ address,
+ addressInfo.prefixLength,
+ 0 /* flags */,
+ 0 /* scope */,
+ deprecationTimeMillis,
+ LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 9b1c338..8c63d37 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
@@ -33,6 +34,7 @@
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,6 +45,8 @@
import android.content.Context;
import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.thread.utils.FullThreadDevice;
@@ -55,6 +59,7 @@
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
@@ -252,6 +257,21 @@
}
@Test
+ public void unicastRouting_meshLocalAddressesAreNotPreferred() throws Exception {
+ // When BR is enabled, there will be OMR address, so the mesh-local addresses are expected
+ // to be deprecated.
+ List<LinkAddress> linkAddresses = getIpv6LinkAddresses("thread-wpan");
+ IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
+
+ for (LinkAddress address : linkAddresses) {
+ if (meshLocalPrefix.contains(address.getAddress())) {
+ assertThat(address.getDeprecationTime()).isAtMost(SystemClock.elapsedRealtime());
+ assertThat(address.isPreferred()).isFalse();
+ }
+ }
+ }
+
+ @Test
@RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
throws Exception {
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 8f8aa5f..1410d41 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -21,6 +21,7 @@
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.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
@@ -30,6 +31,8 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.OtDaemonController;
import android.net.thread.utils.ThreadFeatureCheckerRule;
@@ -201,6 +204,26 @@
assertThat(udpReply).isEqualTo("Hello,Thread");
}
+ @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");
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ 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();
+ }
+ }
+
+ mOtCtl.executeCommand("br enable");
+ }
+
// TODO (b/323300829): add more tests for integration with linux platform and
// ConnectivityService
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 3efdf7d..9be9566 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -25,6 +25,8 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import android.net.InetAddresses;
+import android.net.LinkAddress;
import android.net.TestNetworkInterface;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
@@ -291,6 +293,20 @@
return false;
}
+ public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) throws IOException {
+ List<LinkAddress> addresses = new ArrayList<>();
+ final String cmd = " ip -6 addr show dev " + interfaceName;
+ final String output = runShellCommandOrThrow(cmd);
+
+ for (final String line : output.split("\\n")) {
+ if (line.contains("inet6")) {
+ addresses.add(parseAddressLine(line));
+ }
+ }
+
+ return addresses;
+ }
+
/** Return the first discovered service of {@code serviceType}. */
public static NsdServiceInfo discoverService(NsdManager nsdManager, String serviceType)
throws Exception {
@@ -392,4 +408,29 @@
@Override
public void onServiceInfoCallbackUnregistered() {}
}
+
+ /**
+ * Parses a line of output from "ip -6 addr show" into a {@link LinkAddress}.
+ *
+ * <p>Example line: "inet6 2001:db8:1:1::1/64 scope global deprecated"
+ */
+ private static LinkAddress parseAddressLine(String line) {
+ String[] parts = line.trim().split("\\s+");
+ String addressString = parts[1];
+ String[] pieces = addressString.split("/", 2);
+ int prefixLength = Integer.parseInt(pieces[1]);
+ final InetAddress address = InetAddresses.parseNumericAddress(pieces[0]);
+ long deprecationTimeMillis =
+ line.contains("deprecated")
+ ? SystemClock.elapsedRealtime()
+ : LinkAddress.LIFETIME_PERMANENT;
+
+ return new LinkAddress(
+ address,
+ prefixLength,
+ 0 /* flags */,
+ 0 /* scope */,
+ deprecationTimeMillis,
+ LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
+ }
}