Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8eacfacb authored by Lorenzo Colitti's avatar Lorenzo Colitti
Browse files

Make it possible to shut down and recreate IpClient in a test.

This is not currently possible because waitForRouterSolicitation
only actually waits for an RS the first time it runs.  This is
because it returns immediately, without waiting, if a RS was
received at any time since the test started. This was done
because dual-stack provisioning tests were flaky, because
getNextDhcpPacket would sometimes consume the RS.

Fix this by making getNextDhcpPacket and getNextArpPacket use
ReadHeads on the TapPacketReader, and going back to using poll in
waitForRouterSolicitation.

Given that waitForRouterSolicitation was the only user of
TapPacketReader#getReceivedPackets, replace that method with a
newReadHead function which seems easier to use.

Bug: 161330494
Test: atest NetworkStackNextIntegrationTests:IpClientIntegrationTest
Change-Id: I7f3ea4f21b80958f5704375ee7a0b17b25720d9d
parent 9e6aeea3
Loading
Loading
Loading
Loading
+38 −29
Original line number Diff line number Diff line
@@ -132,6 +132,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.StateMachine;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.common.ShimUtils;
@@ -179,6 +180,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import kotlin.Lazy;
import kotlin.LazyKt;

/**
 * Tests for IpClient.
 */
@@ -221,6 +225,15 @@ public class IpClientIntegrationTest {
    private Dependencies mDependencies;
    private byte[] mClientMac;

    // ReadHeads for various packet streams. Cannot be initialized in @Before because ReadHead is
    // single-thread-only, and AndroidJUnitRunner runs @Before and @Test on different threads.
    // While it looks like these are created only once per test, they are actually created once per
    // test method because JUnit recreates a fresh test class instance before every test method.
    private Lazy<ArrayTrackRecord<byte[]>.ReadHead> mDhcpPacketReadHead =
            LazyKt.lazy(() -> mPacketReader.getReceivedPackets().newReadHead());
    private Lazy<ArrayTrackRecord<byte[]>.ReadHead> mArpPacketReadHead =
            LazyKt.lazy(() -> mPacketReader.getReceivedPackets().newReadHead());

    // Ethernet header
    private static final int ETH_HEADER_LEN = 14;

@@ -466,6 +479,17 @@ public class IpClientIntegrationTest {
        mHandler.post(() -> mPacketReader.start());
    }

    private IpClient makeIpClient() throws Exception {
        IpClient ipc = new IpClient(mContext, mIfaceName, mCb, mNetworkObserverRegistry,
                mNetworkStackServiceManager, mDependencies);
        // Wait for IpClient to enter its initial state. Otherwise, additional setup steps or tests
        // that mock IpClient's dependencies might interact with those mocks while IpClient is
        // starting. This would cause UnfinishedStubbingExceptions as mocks cannot be interacted
        // with while they are being stubbed.
        HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
        return ipc;
    }

    private void setUpIpClient() throws Exception {
        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        final IBinder netdIBinder =
@@ -476,13 +500,7 @@ public class IpClientIntegrationTest {

        mNetworkObserverRegistry = new NetworkObserverRegistry();
        mNetworkObserverRegistry.register(mNetd);
        mIpc = new IpClient(mContext, mIfaceName, mCb, mNetworkObserverRegistry,
                mNetworkStackServiceManager, mDependencies);
        // Wait for IpClient to enter its initial state. Otherwise, additional setup steps or tests
        // that mock IpClient's dependencies might interact with those mocks while IpClient is
        // starting. This would cause UnfinishedStubbingExceptions as mocks cannot be interacted
        // with while they are being stubbed.
        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
        mIpc = makeIpClient();

        // Tell the IpMemoryStore immediately to answer any question about network attributes with a
        // null response. Otherwise, the DHCP client will wait for two seconds before starting,
@@ -613,12 +631,15 @@ public class IpClientIntegrationTest {
        mPacketReader.sendResponse(packet);
    }

    private void startIpClientProvisioning(final ProvisioningConfiguration cfg) throws Exception {
        mIpc.startProvisioning(cfg);
    }

    private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
            final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled,
            final boolean isDhcpIpConflictDetectEnabled,
            final boolean isHostnameConfigurationEnabled, final String hostname,
            final String displayName, final ScanResultInfo scanResultInfo)
            throws RemoteException {
            final String displayName, final ScanResultInfo scanResultInfo) throws Exception {
        ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
                .withoutIpReachabilityMonitor()
                .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -632,7 +653,7 @@ public class IpClientIntegrationTest {
        mDependencies.setDhcpRapidCommitEnabled(shouldReplyRapidCommitAck);
        mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled);
        mDependencies.setHostnameConfiguration(isHostnameConfigurationEnabled, hostname);
        mIpc.startProvisioning(prov.build());
        startIpClientProvisioning(prov.build());
        if (!isPreconnectionEnabled) {
            verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
        }
@@ -641,8 +662,7 @@ public class IpClientIntegrationTest {

    private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
            final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled,
            final boolean isDhcpIpConflictDetectEnabled)
            throws RemoteException {
            final boolean isDhcpIpConflictDetectEnabled)  throws Exception {
        startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
                isPreconnectionEnabled, isDhcpIpConflictDetectEnabled,
                false /* isHostnameConfigurationEnabled */, null /* hostname */,
@@ -764,14 +784,10 @@ public class IpClientIntegrationTest {
    }

    private DhcpPacket getNextDhcpPacket() throws ParseException {
        byte[] packet;
        while ((packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS)) != null) {
            if (!isDhcpPacket(packet)) continue;
        byte[] packet = mDhcpPacketReadHead.getValue().poll(PACKET_TIMEOUT_MS, this::isDhcpPacket);
        assertNotNull("No expected DHCP packet received on interface within timeout", packet);
        return DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L2);
    }
        fail("No expected DHCP packet received on interface within timeout");
        return null;
    }

    private DhcpPacket getReplyFromDhcpLease(final NetworkAttributes na, boolean timeout)
            throws Exception {
@@ -947,7 +963,7 @@ public class IpClientIntegrationTest {

    private ArpPacket getNextArpPacket(final int timeout) throws Exception {
        byte[] packet;
        while ((packet = mPacketReader.popPacket(timeout)) != null) {
        while ((packet = mArpPacketReadHead.getValue().poll(timeout, p -> true)) != null) {
            final ArpPacket arpPacket = parseArpPacketOrNull(packet);
            if (arpPacket != null) return arpPacket;
        }
@@ -1288,16 +1304,9 @@ public class IpClientIntegrationTest {
                        == (byte) ICMPV6_ROUTER_SOLICITATION;
    }

    /**
     * Wait for any router solicitation to have arrived since the packet reader was received.
     *
     * This method does not affect packets obtained via mPacketReader.popPacket. After any router
     * solicitation has been received, calls to this method will just return immediately.
     */
    private void waitForRouterSolicitation() {
    private void waitForRouterSolicitation() throws ParseException {
        assertNotNull("No router solicitation received on interface within timeout",
                mPacketReader.getReceivedPackets().poll(
                        PACKET_TIMEOUT_MS, 0 /* pos */, this::isRouterSolicitation));
                mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation));
    }

    private void sendRouterAdvertisement(boolean waitForRs, short lifetime) throws Exception {