Add netdata prefixes to tun interface routing table.

When there are thread devices with DUA in the thread network, BR itself
may not have a DUA address, so it doesn't have route to DUA in the
thread-wpan routing table. When thread device sends a packet from the
DUA to AIL via BR, BR rpfilter would drop the packet. Adding the route
to DUA prefix in thread-wpan solves this problem.

Bug: 335762753

Test: atest ThreadNetworkIntegrationTests:android.net.thread.ThreadIntegrationTest#addPrefixToNetData_routeIsAddedToTunInterface
atest ThreadNetworkIntegrationTests:android.net.thread.ThreadIntegrationTest#removePrefixFromNetData_routeIsRemovedFromTunInterface

Change-Id: I5ca8f7e723116a63718734f72f1b463d9c380329
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0559499..05d390d 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -121,6 +121,7 @@
 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;
@@ -1159,6 +1160,18 @@
         }
     }
 
+    private void handlePrefixChanged(List<OnMeshPrefixConfig> 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;
@@ -1507,5 +1520,10 @@
         public void onBackboneRouterStateChanged(BackboneRouterState state) {
             mHandler.post(() -> handleMulticastForwardingChanged(state));
         }
+
+        @Override
+        public void onPrefixChanged(List<OnMeshPrefixConfig> 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 dec72b2..c3f6ace 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.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 @@
     private static int sNetlinkSeqNo = 0;
     private final MulticastSocket mMulticastSocket; // For join group and leave group
     private NetworkInterface mNetworkInterface;
-    private List<InetAddress> mMulticastAddresses = new ArrayList<>();
+    private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
+    private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
 
     /** Creates a new {@link TunInterfaceController} instance for given interface. */
     public TunInterfaceController(String interfaceName) {
@@ -243,13 +245,40 @@
         mMulticastAddresses.addAll(newMulticastAddresses);
     }
 
+    public void updatePrefixes(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+        final List<RouteInfo> newNetDataPrefixes = new ArrayList<>();
+
+        for (OnMeshPrefixConfig onMeshPrefixConfig : onMeshPrefixConfigList) {
+            newNetDataPrefixes.add(getRouteForOnMeshPrefix(onMeshPrefixConfig));
+        }
+
+        final CompareResult<RouteInfo> 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 998e70d..4028014 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.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.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 @@
     // 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 @@
     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 @@
         }
     }
 
+    @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 9be9566..78f5770 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.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 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<Network> 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) {}