diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java index 2fccf6bdfc..1f27f068d4 100644 --- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java +++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java @@ -18,19 +18,22 @@ package android.net.thread; import static android.Manifest.permission.MANAGE_TEST_NETWORKS; import static android.Manifest.permission.NETWORK_SETTINGS; -import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER; import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED; import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT; import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT; import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet; +import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source; +import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup; import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported; +import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination; import static android.net.thread.utils.IntegrationTestUtils.newPacketReader; -import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom; +import static android.net.thread.utils.IntegrationTestUtils.pollForPacket; import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage; import static android.net.thread.utils.IntegrationTestUtils.waitFor; -import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; +import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast; import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork; import static com.android.testutils.TestPermissionUtil.runAsShell; @@ -39,12 +42,14 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assume.assumeNotNull; import static org.junit.Assume.assumeTrue; import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.content.Context; +import android.net.InetAddresses; import android.net.LinkProperties; import android.net.MacAddress; import android.net.thread.utils.FullThreadDevice; @@ -66,10 +71,12 @@ import org.junit.runner.RunWith; import java.net.Inet6Address; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; /** Integration test cases for Thread Border Routing feature. */ @RunWith(AndroidJUnit4.class) @@ -81,6 +88,18 @@ public class BorderRoutingTest { private HandlerThread mHandlerThread; private Handler mHandler; private TestNetworkTracker mInfraNetworkTracker; + private List mFtds; + private TapPacketReader mInfraNetworkReader; + private InfraNetworkDevice mInfraDevice; + + private static final int NUM_FTD = 2; + private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0"; + private static final Inet6Address GROUP_ADDR_SCOPE_5 = + (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234"); + private static final Inet6Address GROUP_ADDR_SCOPE_4 = + (Inet6Address) InetAddresses.parseNumericAddress("ff04::1234"); + private static final Inet6Address GROUP_ADDR_SCOPE_3 = + (Inet6Address) InetAddresses.parseNumericAddress("ff03::1234"); // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new". private static final byte[] DEFAULT_DATASET_TLVS = @@ -95,6 +114,7 @@ public class BorderRoutingTest { @Before public void setUp() throws Exception { + assumeTrue(isSimulatedThreadRadioSupported()); final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class); if (manager != null) { mController = manager.getAllThreadNetworkControllers().get(0); @@ -106,24 +126,21 @@ public class BorderRoutingTest { mHandlerThread = new HandlerThread(getClass().getSimpleName()); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + mFtds = new ArrayList<>(); - mInfraNetworkTracker = - runAsShell( - MANAGE_TEST_NETWORKS, - () -> - initTestNetwork( - mContext, new LinkProperties(), 5000 /* timeoutMs */)); - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - NETWORK_SETTINGS, - () -> { - CountDownLatch latch = new CountDownLatch(1); - mController.setTestNetworkAsUpstream( - mInfraNetworkTracker.getTestIface().getInterfaceName(), - directExecutor(), - v -> latch.countDown()); - latch.await(); - }); + setUpInfraNetwork(); + + // BR forms a network. + startBrLeader(); + + // Creates a infra network device. + mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler); + startInfraDevice(); + + // Create Ftds + for (int i = 0; i < NUM_FTD; ++i) { + mFtds.add(new FullThreadDevice(15 + i /* node ID */)); + } } @After @@ -142,16 +159,19 @@ public class BorderRoutingTest { mController.leave(directExecutor(), v -> latch.countDown()); latch.await(10, TimeUnit.SECONDS); }); - runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown()); + tearDownInfraNetwork(); mHandlerThread.quitSafely(); mHandlerThread.join(); + + for (var ftd : mFtds) { + ftd.destroy(); + } + mFtds.clear(); } @Test public void unicastRouting_infraDevicePingTheadDeviceOmr_replyReceived() throws Exception { - assumeTrue(isSimulatedThreadRadioSupported()); - /* *
          * Topology:
@@ -161,37 +181,15 @@ public class BorderRoutingTest {
          * 
*/ - // BR forms a network. - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> mController.join(DEFAULT_DATASET, directExecutor(), result -> {})); - waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), JOIN_TIMEOUT); - - // Creates a Full Thread Device (FTD) and lets it join the network. - FullThreadDevice ftd = new FullThreadDevice(5 /* node ID */); - ftd.factoryReset(); - ftd.joinNetwork(DEFAULT_DATASET); - ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT); - waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60)); - Inet6Address ftdOmr = ftd.getOmrAddress(); - assertNotNull(ftdOmr); - - // Creates a infra network device. - TapPacketReader infraNetworkReader = - newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler); - InfraNetworkDevice infraDevice = - new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), infraNetworkReader); - infraDevice.runSlaac(Duration.ofSeconds(60)); - assertNotNull(infraDevice.ipv6Addr); + // Let ftd join the network. + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); // Infra device sends an echo request to FTD's OMR. - infraDevice.sendEchoRequest(ftdOmr); + mInfraDevice.sendEchoRequest(ftd.getOmrAddress()); // Infra device receives an echo reply sent by FTD. - assertNotNull( - readPacketFrom( - infraNetworkReader, - p -> isExpectedIcmpv6Packet(p, ICMPV6_ECHO_REPLY_TYPE))); + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, null /* srcAddress */)); } @Test @@ -216,12 +214,9 @@ public class BorderRoutingTest { joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS); // Creates a Full Thread Device (FTD) and lets it join the network. - FullThreadDevice ftd = new FullThreadDevice(6 /* node ID */); - ftd.joinNetwork(DEFAULT_DATASET); - ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT); - waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60)); + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); Inet6Address ftdOmr = ftd.getOmrAddress(); - assertNotNull(ftdOmr); Inet6Address ftdMlEid = ftd.getMlEid(); assertNotNull(ftdMlEid); @@ -233,4 +228,373 @@ public class BorderRoutingTest { sendUdpMessage(ftdMlEid, 12345, "bbbbbbbb"); assertEquals("bbbbbbbb", ftd.udpReceive()); } + + @Test + public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + + ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_5); + + assertInfraLinkMemberOfGroup(GROUP_ADDR_SCOPE_5); + } + + @Test + public void + multicastRouting_ftdSubscribedScope3MulticastAddress_infraLinkNotJoinMulticastGroup() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + + ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3); + + assertInfraLinkNotMemberOfGroup(GROUP_ADDR_SCOPE_3); + } + + @Test + public void multicastRouting_ftdSubscribedMulticastAddress_canPingfromInfraLink() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5); + + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); + + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + } + + @Test + public void multicastRouting_ftdSubscribedScope3MulticastAddress_cannotPingfromInfraLink() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3); + + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3); + + assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + } + + @Test + public void multicastRouting_ftdNotSubscribedMulticastAddress_cannotPingFromInfraDevice() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4); + + assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + } + + @Test + public void multicastRouting_multipleFtdsSubscribedDifferentAddresses_canPingFromInfraDevice() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device 1
+         *                                   (Cuttlefish)
+         *                                         |
+         *                                         | Thread
+         *                                         |
+         *                                  Full Thread device 2
+         * 
+ */ + + FullThreadDevice ftd1 = mFtds.get(0); + startFtdChild(ftd1); + subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5); + + FullThreadDevice ftd2 = mFtds.get(1); + startFtdChild(ftd2); + subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_4); + + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4); + + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); + } + + @Test + public void multicastRouting_multipleFtdsSubscribedSameAddress_canPingFromInfraDevice() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device 1
+         *                                   (Cuttlefish)
+         *                                         |
+         *                                         | Thread
+         *                                         |
+         *                                  Full Thread device 2
+         * 
+ */ + + FullThreadDevice ftd1 = mFtds.get(0); + startFtdChild(ftd1); + subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5); + + FullThreadDevice ftd2 = mFtds.get(1); + startFtdChild(ftd2); + subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_5); + + // Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); + + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); + } + + @Test + public void multicastRouting_outboundForwarding_scopeLargerThan3IsForwarded() throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + Inet6Address ftdOmr = ftd.getOmrAddress(); + + ftd.ping(GROUP_ADDR_SCOPE_5); + ftd.ping(GROUP_ADDR_SCOPE_4); + + assertNotNull( + pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5)); + assertNotNull( + pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); + } + + @Test + public void multicastRouting_outboundForwarding_scopeSmallerThan4IsNotForwarded() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + + ftd.ping(GROUP_ADDR_SCOPE_3); + + assertNull( + pollForPacketOnInfraNetwork( + ICMPV6_ECHO_REQUEST_TYPE, ftd.getOmrAddress(), GROUP_ADDR_SCOPE_3)); + } + + @Test + public void multicastRouting_infraNetworkSwitch_ftdRepliesToSubscribedAddress() + throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5); + Inet6Address ftdOmr = ftd.getOmrAddress(); + + // Destroy infra link and re-create + tearDownInfraNetwork(); + setUpInfraNetwork(); + mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler); + startInfraDevice(); + + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); + + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr)); + } + + @Test + public void multicastRouting_infraNetworkSwitch_outboundPacketIsForwarded() throws Exception { + assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)); + /* + *
+         * Topology:
+         *                 infra network                       Thread
+         * infra device -------------------- Border Router -------------- Full Thread device
+         *                                   (Cuttlefish)
+         * 
+ */ + + FullThreadDevice ftd = mFtds.get(0); + startFtdChild(ftd); + Inet6Address ftdOmr = ftd.getOmrAddress(); + + // Destroy infra link and re-create + tearDownInfraNetwork(); + setUpInfraNetwork(); + mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler); + startInfraDevice(); + + ftd.ping(GROUP_ADDR_SCOPE_5); + ftd.ping(GROUP_ADDR_SCOPE_4); + + assertNotNull( + pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5)); + assertNotNull( + pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); + } + + private void setUpInfraNetwork() { + mInfraNetworkTracker = + runAsShell( + MANAGE_TEST_NETWORKS, + () -> + initTestNetwork( + mContext, new LinkProperties(), 5000 /* timeoutMs */)); + runAsShell( + PERMISSION_THREAD_NETWORK_PRIVILEGED, + NETWORK_SETTINGS, + () -> { + CompletableFuture future = new CompletableFuture<>(); + mController.setTestNetworkAsUpstream( + mInfraNetworkTracker.getTestIface().getInterfaceName(), + directExecutor(), + future::complete); + future.get(5, TimeUnit.SECONDS); + }); + } + + private void tearDownInfraNetwork() { + runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown()); + } + + private void startBrLeader() throws Exception { + CompletableFuture joinFuture = new CompletableFuture<>(); + runAsShell( + PERMISSION_THREAD_NETWORK_PRIVILEGED, + () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete)); + joinFuture.get(RESTART_JOIN_TIMEOUT.toSeconds(), TimeUnit.SECONDS); + } + + private void startFtdChild(FullThreadDevice ftd) throws Exception { + ftd.factoryReset(); + ftd.joinNetwork(DEFAULT_DATASET); + ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT); + waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60)); + Inet6Address ftdOmr = ftd.getOmrAddress(); + assertNotNull(ftdOmr); + } + + private void startInfraDevice() throws Exception { + mInfraDevice = + new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), mInfraNetworkReader); + mInfraDevice.runSlaac(Duration.ofSeconds(60)); + assertNotNull(mInfraDevice.ipv6Addr); + } + + private void assertInfraLinkMemberOfGroup(Inet6Address address) throws Exception { + waitFor( + () -> + isInMulticastGroup( + mInfraNetworkTracker.getTestIface().getInterfaceName(), address), + Duration.ofSeconds(3)); + } + + private void assertInfraLinkNotMemberOfGroup(Inet6Address address) throws Exception { + waitFor( + () -> + !isInMulticastGroup( + mInfraNetworkTracker.getTestIface().getInterfaceName(), address), + Duration.ofSeconds(3)); + } + + private void subscribeMulticastAddressAndWait(FullThreadDevice ftd, Inet6Address address) + throws Exception { + ftd.subscribeMulticastAddress(address); + + assertInfraLinkMemberOfGroup(address); + } + + private byte[] pollForPacketOnInfraNetwork(int type, Inet6Address srcAddress) { + return pollForPacketOnInfraNetwork(type, srcAddress, null); + } + + private byte[] pollForPacketOnInfraNetwork( + int type, Inet6Address srcAddress, Inet6Address destAddress) { + Predicate filter; + filter = + p -> + (isExpectedIcmpv6Packet(p, type) + && (srcAddress == null ? true : isFromIpv6Source(p, srcAddress)) + && (destAddress == null + ? true + : isToIpv6Destination(p, destAddress))); + return pollForPacket(mInfraNetworkReader, filter); + } } diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java index 5ca40e359c..bb0034afd9 100644 --- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java +++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java @@ -75,6 +75,10 @@ public final class FullThreadDevice { mActiveOperationalDataset = null; } + public void destroy() { + mProcess.destroy(); + } + /** * Returns an OMR (Off-Mesh-Routable) address on this device if any. * @@ -182,6 +186,27 @@ public final class FullThreadDevice { } } + public void subscribeMulticastAddress(Inet6Address address) { + executeCommand("ipmaddr add " + address.getHostAddress()); + } + + public void ping(Inet6Address address, Inet6Address source, int size, int count) { + String cmd = + "ping" + + ((source == null) ? "" : (" -I " + source.getHostAddress())) + + " " + + address.getHostAddress() + + " " + + size + + " " + + count; + executeCommand(cmd); + } + + public void ping(Inet6Address address) { + ping(address, null, 100 /* size */, 1 /* count */); + } + private List executeCommand(String command) { try { mWriter.write(command + "\n"); diff --git a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java index 3081f9fc14..72a278c2fe 100644 --- a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java +++ b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java @@ -16,7 +16,7 @@ package android.net.thread.utils; import static android.net.thread.utils.IntegrationTestUtils.getRaPios; -import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom; +import static android.net.thread.utils.IntegrationTestUtils.pollForPacket; import static android.net.thread.utils.IntegrationTestUtils.waitFor; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; @@ -109,7 +109,7 @@ public final class InfraNetworkDevice { try { sendRsPacket(); - final byte[] raPacket = readPacketFrom(packetReader, p -> !getRaPios(p).isEmpty()); + final byte[] raPacket = pollForPacket(packetReader, p -> !getRaPios(p).isEmpty()); final List options = getRaPios(raPacket); 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 4eef0e5d80..74251a6e7c 100644 --- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java +++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java @@ -17,6 +17,7 @@ package android.net.thread.utils; import static android.system.OsConstants.IPPROTO_ICMPV6; +import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; @@ -42,6 +43,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -149,17 +151,17 @@ public final class IntegrationTestUtils { } /** - * Reads a packet from a given {@link TapPacketReader} that satisfies the {@code filter}. + * Polls for a packet from a given {@link TapPacketReader} that satisfies the {@code filter}. * * @param packetReader a TUN packet reader * @param filter the filter to be applied on the packet * @return the first IPv6 packet that satisfies the {@code filter}. If it has waited for more * than 3000ms to read the next packet, the method will return null */ - public static byte[] readPacketFrom(TapPacketReader packetReader, Predicate filter) { + public static byte[] pollForPacket(TapPacketReader packetReader, Predicate filter) { byte[] packet; - while ((packet = packetReader.poll(3000 /* timeoutMs */)) != null) { - if (filter.test(packet)) return packet; + while ((packet = packetReader.poll(3000 /* timeoutMs */, filter)) != null) { + return packet; } return null; } @@ -182,6 +184,34 @@ public final class IntegrationTestUtils { return false; } + public static boolean isFromIpv6Source(byte[] packet, Inet6Address src) { + if (packet == null) { + return false; + } + ByteBuffer buf = ByteBuffer.wrap(packet); + try { + return Struct.parse(Ipv6Header.class, buf).srcIp.equals(src); + } catch (IllegalArgumentException ignored) { + // It's fine that the passed in packet is malformed because it's could be sent + // by anybody. + } + return false; + } + + public static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) { + if (packet == null) { + return false; + } + ByteBuffer buf = ByteBuffer.wrap(packet); + try { + return Struct.parse(Ipv6Header.class, buf).dstIp.equals(dest); + } catch (IllegalArgumentException ignored) { + // It's fine that the passed in packet is malformed because it's could be sent + // by anybody. + } + return false; + } + /** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */ public static List getRaPios(byte[] raMsg) { final ArrayList pioList = new ArrayList<>(); @@ -247,4 +277,16 @@ public final class IntegrationTestUtils { socket.send(packet); } } + + public static boolean isInMulticastGroup(String interfaceName, Inet6Address address) { + final String cmd = "ip -6 maddr show dev " + interfaceName; + final String output = runShellCommandOrThrow(cmd); + final String addressStr = address.getHostAddress(); + for (final String line : output.split("\\n")) { + if (line.contains(addressStr)) { + return true; + } + } + return false; + } }