[Test] Thread multicast routing E2E test
Added E2E test for Thread multicast routing.
Bug: 320616891
Test: atest ThreadNetworkIntegrationTests:android.net.thread.BorderRoutingTest
Change-Id: Icbaa9f6645080bd1a0a95ddc7902b9a468dd0088
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 2fccf6b..1f27f06 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 @@
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 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 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 @@
private HandlerThread mHandlerThread;
private Handler mHandler;
private TestNetworkTracker mInfraNetworkTracker;
+ private List<FullThreadDevice> 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 @@
@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 @@
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 @@
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());
-
/*
* <pre>
* Topology:
@@ -161,37 +181,15 @@
* </pre>
*/
- // 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 @@
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 @@
sendUdpMessage(ftdMlEid, 12345, "bbbbbbbb");
assertEquals("bbbbbbbb", ftd.udpReceive());
}
+
+ @Test
+ public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device 1
+ * (Cuttlefish)
+ * |
+ * | Thread
+ * |
+ * Full Thread device 2
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device 1
+ * (Cuttlefish)
+ * |
+ * | Thread
+ * |
+ * Full Thread device 2
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ 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<Void> 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<Void> 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<byte[]> 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 5ca40e3..bb0034a 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 @@
mActiveOperationalDataset = null;
}
+ public void destroy() {
+ mProcess.destroy();
+ }
+
/**
* Returns an OMR (Off-Mesh-Routable) address on this device if any.
*
@@ -182,6 +186,27 @@
}
}
+ 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<String> 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 3081f9f..72a278c 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 @@
try {
sendRsPacket();
- final byte[] raPacket = readPacketFrom(packetReader, p -> !getRaPios(p).isEmpty());
+ final byte[] raPacket = pollForPacket(packetReader, p -> !getRaPios(p).isEmpty());
final List<PrefixInformationOption> 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 4eef0e5..74251a6 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 @@
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.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 @@
}
/**
- * 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<byte[]> filter) {
+ public static byte[] pollForPacket(TapPacketReader packetReader, Predicate<byte[]> 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 @@
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<PrefixInformationOption> getRaPios(byte[] raMsg) {
final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
@@ -247,4 +277,16 @@
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;
+ }
}