From 79b202ac9fe589ea2e52a8f3bcaa176e30d49189 Mon Sep 17 00:00:00 2001 From: Yang Sun Date: Fri, 29 Mar 2024 00:47:27 -0700 Subject: [PATCH] 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 --- .../ThreadNetworkControllerService.java | 54 +------------ .../server/thread/TunInterfaceController.java | 79 +++++++++++++++++++ .../android/net/thread/BorderRoutingTest.java | 20 +++++ .../net/thread/ThreadIntegrationTest.java | 23 ++++++ .../thread/utils/IntegrationTestUtils.java | 41 ++++++++++ 5 files changed, 167 insertions(+), 50 deletions(-) diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index 63cd574066..7c757c0767 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.IntentFilter; 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.HandlerThread; 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 libcore.util.HexEncoding; 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 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub 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 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub } } - private void handleAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) { + private void handleAddressChanged(List 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 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub } @Override - public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) { - mHandler.post(() -> handleAddressChanged(addressInfo, isAdded)); + public void onAddressChanged(List 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 b29a54f03a..97cdd5579c 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.Os; 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 class TunInterfaceController { } } + public void updateAddresses(List addressInfoList) { + final List 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 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 @@ public class TunInterfaceController { 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 9b1c3389da..8c63d3751b 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 @@ package android.net.thread; 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.TestNetworkTrackerKt.initTestNetwork; 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 static java.util.Objects.requireNonNull; 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.ThreadFeatureCheckerRule.RequiresThreadFeature; 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; @@ -251,6 +256,21 @@ public class BorderRoutingTest { assertEquals("bbbbbbbb", ftd.udpReceive()); } + @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 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() diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java index 8f8aa5f188..1410d41987 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_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.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.io.BaseEncoding.base16; 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 @@ public class ThreadIntegrationTest { 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 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 3efdf7d829..9be9566f42 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 com.google.common.util.concurrent.MoreExecutors.directExecutor; 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 @@ public final class IntegrationTestUtils { return false; } + public static List getIpv6LinkAddresses(String interfaceName) throws IOException { + List 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 @@ public final class IntegrationTestUtils { @Override public void onServiceInfoCallbackUnregistered() {} } + + /** + * Parses a line of output from "ip -6 addr show" into a {@link LinkAddress}. + * + *

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 */); + } }