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
This commit is contained in:
Yang Sun 2024-03-29 00:47:27 -07:00
commit 79b202ac9f
5 changed files with 167 additions and 50 deletions

View file

@ -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<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 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
}
@Override
public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
mHandler.post(() -> handleAddressChanged(addressInfo, isAdded));
public void onAddressChanged(List<Ipv6AddressInfo> addressInfoList) {
mHandler.post(() -> handleAddressChanged(addressInfoList));
}
@Override

View file

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

View file

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

View file

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

View file

@ -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<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 @@ public final class IntegrationTestUtils {
@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 */);
}
}