diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index d8018aaaa1..0c200fd18f 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -122,6 +122,7 @@ import com.android.server.thread.openthread.IOtDaemonCallback; import com.android.server.thread.openthread.IOtStatusReceiver; import com.android.server.thread.openthread.Ipv6AddressInfo; import com.android.server.thread.openthread.MeshcopTxtAttributes; +import com.android.server.thread.openthread.OnMeshPrefixConfig; import com.android.server.thread.openthread.OtDaemonState; import libcore.util.HexEncoding; @@ -1234,6 +1235,18 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub } } + private void handlePrefixChanged(List onMeshPrefixConfigList) { + checkOnHandlerThread(); + + mTunIfController.updatePrefixes(onMeshPrefixConfigList); + + // The OT daemon can send link property updates before the networkAgent is + // registered + if (mNetworkAgent != null) { + mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties()); + } + } + private void sendLocalNetworkConfig() { if (mNetworkAgent == null) { return; @@ -1581,5 +1594,10 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub public void onBackboneRouterStateChanged(BackboneRouterState state) { mHandler.post(() -> handleMulticastForwardingChanged(state)); } + + @Override + public void onPrefixChanged(List onMeshPrefixConfigList) { + mHandler.post(() -> handlePrefixChanged(onMeshPrefixConfigList)); + } } } diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java index dec72b260d..c3f6ace3f6 100644 --- a/thread/service/java/com/android/server/thread/TunInterfaceController.java +++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java @@ -35,6 +35,7 @@ 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 com.android.server.thread.openthread.OnMeshPrefixConfig; import java.io.FileDescriptor; import java.io.IOException; @@ -66,7 +67,8 @@ public class TunInterfaceController { private static int sNetlinkSeqNo = 0; private final MulticastSocket mMulticastSocket; // For join group and leave group private NetworkInterface mNetworkInterface; - private List mMulticastAddresses = new ArrayList<>(); + private final List mMulticastAddresses = new ArrayList<>(); + private final List mNetDataPrefixes = new ArrayList<>(); /** Creates a new {@link TunInterfaceController} instance for given interface. */ public TunInterfaceController(String interfaceName) { @@ -243,13 +245,40 @@ public class TunInterfaceController { mMulticastAddresses.addAll(newMulticastAddresses); } + public void updatePrefixes(List onMeshPrefixConfigList) { + final List newNetDataPrefixes = new ArrayList<>(); + + for (OnMeshPrefixConfig onMeshPrefixConfig : onMeshPrefixConfigList) { + newNetDataPrefixes.add(getRouteForOnMeshPrefix(onMeshPrefixConfig)); + } + + final CompareResult prefixDiff = + new CompareResult<>(mNetDataPrefixes, newNetDataPrefixes); + for (RouteInfo routeRemoved : prefixDiff.removed) { + mLinkProperties.removeRoute(routeRemoved); + } + for (RouteInfo routeAdded : prefixDiff.added) { + mLinkProperties.addRoute(routeAdded); + } + + mNetDataPrefixes.clear(); + mNetDataPrefixes.addAll(newNetDataPrefixes); + } + private RouteInfo getRouteForAddress(LinkAddress linkAddress) { - return new RouteInfo( - new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()), - null, - mIfName, - RouteInfo.RTN_UNICAST, - MTU); + return getRouteForIpPrefix( + new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength())); + } + + private RouteInfo getRouteForOnMeshPrefix(OnMeshPrefixConfig onMeshPrefixConfig) { + return getRouteForIpPrefix( + new IpPrefix( + bytesToInet6Address(onMeshPrefixConfig.prefix), + onMeshPrefixConfig.prefixLength)); + } + + private RouteInfo getRouteForIpPrefix(IpPrefix ipPrefix) { + return new RouteInfo(ipPrefix, null, mIfName, RouteInfo.RTN_UNICAST, MTU); } /** Called by {@link ThreadNetworkControllerService} to do clean up when ot-daemon is dead. */ diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java index 998e70d9ff..4028014e38 100644 --- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java +++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java @@ -22,6 +22,8 @@ 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.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; @@ -34,9 +36,11 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; 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.thread.utils.FullThreadDevice; import android.net.thread.utils.OtDaemonController; import android.net.thread.utils.ThreadFeatureCheckerRule; @@ -76,6 +80,9 @@ public class ThreadIntegrationTest { // The maximum time for OT addresses to be propagated to the TUN interface "thread-wpan" private static final Duration TUN_ADDR_UPDATE_TIMEOUT = Duration.ofSeconds(1); + // The maximum time for changes to be propagated to netdata. + private static final Duration NET_DATA_UPDATE_TIMEOUT = Duration.ofSeconds(1); + // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new". private static final byte[] DEFAULT_DATASET_TLVS = base16().decode( @@ -90,6 +97,10 @@ public class ThreadIntegrationTest { private static final Inet6Address GROUP_ADDR_ALL_ROUTERS = (Inet6Address) InetAddresses.parseNumericAddress("ff02::2"); + private static final String TEST_NO_SLAAC_PREFIX = "9101:dead:beef:cafe::/64"; + private static final InetAddress TEST_NO_SLAAC_PREFIX_ADDRESS = + InetAddresses.parseNumericAddress("9101:dead:beef:cafe::"); + @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule(); private ExecutorService mExecutor; @@ -253,6 +264,49 @@ public class ThreadIntegrationTest { } } + @Test + public void addPrefixToNetData_routeIsAddedToTunInterface() throws Exception { + ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + mController.joinAndWait(DEFAULT_DATASET); + + // Ftd child doesn't have the ability to add a prefix, so let BR itself add a prefix. + mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med"); + mOtCtl.executeCommand("netdata register"); + waitFor( + () -> { + String netData = mOtCtl.executeCommand("netdata show"); + return getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX); + }, + NET_DATA_UPDATE_TIMEOUT); + + LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT)); + assertThat(lp).isNotNull(); + assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS))) + .isTrue(); + } + + @Test + public void removePrefixFromNetData_routeIsRemovedFromTunInterface() throws Exception { + ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + mController.joinAndWait(DEFAULT_DATASET); + mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med"); + mOtCtl.executeCommand("netdata register"); + + mOtCtl.executeCommand("prefix remove " + TEST_NO_SLAAC_PREFIX); + mOtCtl.executeCommand("netdata register"); + waitFor( + () -> { + String netData = mOtCtl.executeCommand("netdata show"); + return !getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX); + }, + NET_DATA_UPDATE_TIMEOUT); + + LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT)); + assertThat(lp).isNotNull(); + assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS))) + .isFalse(); + } + // 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 9be9566f42..78f57707bd 100644 --- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java +++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java @@ -15,6 +15,7 @@ */ package android.net.thread.utils; +import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK; import static android.system.OsConstants.IPPROTO_ICMPV6; import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; @@ -24,17 +25,24 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_AD import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.LinkAddress; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.net.TestNetworkInterface; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.net.thread.ThreadNetworkController; +import android.os.Build; import android.os.Handler; import android.os.SystemClock; import androidx.annotation.NonNull; +import androidx.test.core.app.ApplicationProvider; import com.android.net.module.util.Struct; import com.android.net.module.util.structs.Icmpv6Header; @@ -375,6 +383,36 @@ public final class IntegrationTestUtils { } } + public static String getPrefixesFromNetData(String netData) { + int startIdx = netData.indexOf("Prefixes:"); + int endIdx = netData.indexOf("Routes:"); + return netData.substring(startIdx, endIdx); + } + + public static Network getThreadNetwork(Duration timeout) throws Exception { + CompletableFuture networkFuture = new CompletableFuture<>(); + ConnectivityManager cm = + ApplicationProvider.getApplicationContext() + .getSystemService(ConnectivityManager.class); + NetworkRequest.Builder networkRequestBuilder = + new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_THREAD); + // Before V, we need to explicitly set `NET_CAPABILITY_LOCAL_NETWORK` capability to request + // a Thread network. + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + networkRequestBuilder.addCapability(NET_CAPABILITY_LOCAL_NETWORK); + } + NetworkRequest networkRequest = networkRequestBuilder.build(); + ConnectivityManager.NetworkCallback networkCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + networkFuture.complete(network); + } + }; + cm.registerNetworkCallback(networkRequest, networkCallback); + return networkFuture.get(timeout.toSeconds(), SECONDS); + } + private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener { @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) {}