[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
This commit is contained in:
Yang Sun 2024-01-18 19:19:43 +08:00
commit e056b9a188
4 changed files with 492 additions and 61 deletions

View file

@ -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<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 @@ 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());
/*
* <pre>
* Topology:
@ -161,37 +181,15 @@ public class BorderRoutingTest {
* </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 @@ 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));
/*
* <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);
}
}

View file

@ -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<String> executeCommand(String command) {
try {
mWriter.write(command + "\n");

View file

@ -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<PrefixInformationOption> options = getRaPios(raPacket);

View file

@ -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<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 @@ 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<PrefixInformationOption> getRaPios(byte[] raMsg) {
final ArrayList<PrefixInformationOption> 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;
}
}