[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:
parent
e521ae306a
commit
e056b9a188
4 changed files with 492 additions and 61 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue