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

Commit b3069585 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Renew previous IP address when roaming happens in certain specific...

Merge "Renew previous IP address when roaming happens in certain specific Wi-Fi networks." into rvc-dev
parents 66876ec6 39cd852a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -233,6 +233,9 @@ public class DhcpClient extends StateMachine {
    public static final int CMD_START_PRECONNECTION         = PUBLIC_BASE + 10;
    public static final int CMD_ABORT_PRECONNECTION         = PUBLIC_BASE + 11;

    // Command to rebind the leased IPv4 address on L2 roaming happened.
    public static final int CMD_REFRESH_LINKADDRESS         = PUBLIC_BASE + 12;

    /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
    public static final int DHCP_SUCCESS = 1;
    public static final int DHCP_FAILURE = 2;
@@ -1674,6 +1677,9 @@ public class DhcpClient extends StateMachine {
                case CMD_RENEW_DHCP:
                    preDhcpTransitionTo(mWaitBeforeRenewalState, mDhcpRenewingState);
                    return HANDLED;
                case CMD_REFRESH_LINKADDRESS:
                    transitionTo(mDhcpRebindingState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
+2 −1
Original line number Diff line number Diff line
@@ -335,7 +335,8 @@ public abstract class DhcpPacket {
     * proposed by the client (from an earlier DHCP negotiation) or
     * supplied by the server.
     */
    protected final Inet4Address mClientIp;
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public final Inet4Address mClientIp;
    protected final Inet4Address mYourIp;
    private final Inet4Address mNextIp;
    protected final Inet4Address mRelayIp;
+68 −4
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.net.Layer2InformationParcelable;
import android.net.Layer2PacketParcelable;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.NattKeepalivePacketDataParcelable;
import android.net.NetworkStackIpMemoryStore;
import android.net.ProvisioningConfigurationParcelable;
@@ -92,6 +93,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -381,7 +383,8 @@ public class IpClient extends StateMachine {
    private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13;
    private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14;
    private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15;
    protected static final int CMD_COMPLETE_PRECONNECTION = 16;
    private static final int CMD_COMPLETE_PRECONNECTION = 16;
    private static final int CMD_UPDATE_L2INFORMATION = 17;

    // Internal commands to use instead of trying to call transitionTo() inside
    // a given State's enter() method. Calling transitionTo() from enter/exit
@@ -423,6 +426,14 @@ public class IpClient extends StateMachine {
                    new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xf2, (byte) 0x06 }
    ));

    // Initialize configurable particular SSID set supporting DHCP Roaming feature. See
    // b/131797393 for more details.
    private static final Set<String> DHCP_ROAMING_SSID_SET = new HashSet<>(
            Arrays.asList(
                    "0001docomo", "ollehWiFi", "olleh GiGa WiFi", "KT WiFi",
                    "KT GiGA WiFi", "marente"
    ));

    private final State mStoppedState = new StoppedState();
    private final State mStoppingState = new StoppingState();
    private final State mClearingIpAddressesState = new ClearingIpAddressesState();
@@ -470,6 +481,7 @@ public class IpClient extends StateMachine {
    private String mGroupHint; // The group hint for this network, for writing into the memory store
    private boolean mMulticastFiltering;
    private long mStartTimeMillis;
    private MacAddress mCurrentBssid;

    /**
     * Reading the snapshot is an asynchronous operation initiated by invoking
@@ -704,7 +716,6 @@ public class IpClient extends StateMachine {
            enforceNetworkStackCallingPermission();
            IpClient.this.notifyPreconnectionComplete(success);
        }

        @Override
        public void updateLayer2Information(Layer2InformationParcelable info) {
            enforceNetworkStackCallingPermission();
@@ -772,6 +783,16 @@ public class IpClient extends StateMachine {
            return;
        }

        final ScanResultInfo scanResultInfo = req.mScanResultInfo;
        mCurrentBssid = null;
        if (scanResultInfo != null) {
            try {
                mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid());
            } catch (IllegalArgumentException e) {
                Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid()
                        + " in provisioning configuration", e);
            }
        }
        sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
    }

@@ -819,10 +840,15 @@ public class IpClient extends StateMachine {

    /**
     * Set the L2 key and group hint for storing info into the memory store.
     *
     * This method is only supported on Q devices. For R or above releases,
     * caller should call #updateLayer2Information() instead.
     */
    public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
            sendMessage(CMD_UPDATE_L2KEY_GROUPHINT, new Pair<>(l2Key, groupHint));
        }
    }

    /**
     * Set the HTTP Proxy configuration to use.
@@ -880,7 +906,7 @@ public class IpClient extends StateMachine {
     * Update the network bssid, L2Key and GroupHint layer2 information.
     */
    public void updateLayer2Information(@NonNull Layer2InformationParcelable info) {
        // TODO: add specific implementation.
        sendMessage(CMD_UPDATE_L2INFORMATION, info);
    }

    /**
@@ -1502,6 +1528,36 @@ public class IpClient extends StateMachine {
        }
    }

    private void handleUpdateL2Information(@NonNull Layer2InformationParcelable info) {
        mL2Key = info.l2Key;
        mGroupHint = info.groupHint;

        // This means IpClient is still in the StoppedState, WiFi is trying to associate
        // to the AP, just update L2Key and GroupHint at this stage, because these members
        // will be used when starting DhcpClient.
        if (info.bssid == null || mCurrentBssid == null) return;

        // If the BSSID has not changed, there is nothing to do.
        if (info.bssid.equals(mCurrentBssid)) return;

        if (mIpReachabilityMonitor != null) {
            mIpReachabilityMonitor.probeAll();
        }

        // Check whether to refresh previous IP lease on L2 roaming happened.
        final String ssid = removeDoubleQuotes(mConfiguration.mDisplayName);
        if (DHCP_ROAMING_SSID_SET.contains(ssid) && mDhcpClient != null) {
            if (DBG) {
                Log.d(mTag, "L2 roaming happened from " + mCurrentBssid
                        + " to " + info.bssid
                        + " , SSID: " + ssid
                        + " , starting refresh leased IP address");
            }
            mDhcpClient.sendMessage(DhcpClient.CMD_REFRESH_LINKADDRESS);
        }
        mCurrentBssid = info.bssid;
    }

    class StoppedState extends State {
        @Override
        public void enter() {
@@ -1554,6 +1610,10 @@ public class IpClient extends StateMachine {
                    break;
                }

                case CMD_UPDATE_L2INFORMATION:
                    handleUpdateL2Information((Layer2InformationParcelable) msg.obj);
                    break;

                case CMD_SET_MULTICAST_FILTER:
                    mMulticastFiltering = (boolean) msg.obj;
                    break;
@@ -1763,6 +1823,10 @@ public class IpClient extends StateMachine {
                    break;
                }

                case CMD_UPDATE_L2INFORMATION:
                    handleUpdateL2Information((Layer2InformationParcelable) msg.obj);
                    break;

                case EVENT_PROVISIONING_TIMEOUT:
                    handleProvisioningFailure();
                    break;
+140 −23
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
import static android.net.dhcp.DhcpPacket.ENCAP_L2;
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
import static android.net.ip.IpClient.removeDoubleQuotes;
import static android.net.ipmemorystore.Status.SUCCESS;
import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
@@ -49,7 +48,6 @@ import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICI
import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.server.util.NetworkStackConstants.IPV6_LEN_OFFSET;
import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.server.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;

import static junit.framework.Assert.fail;

@@ -87,6 +85,7 @@ import android.net.INetd;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.Layer2InformationParcelable;
import android.net.Layer2PacketParcelable;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -217,6 +216,8 @@ public class IpClientIntegrationTest {
            (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.100");
    private static final Inet4Address CLIENT_ADDR =
            (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2");
    private static final Inet4Address CLIENT_ADDR_NEW =
            (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.3");
    private static final Inet4Address INADDR_ANY =
            (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0");
    private static final int PREFIX_LENGTH = 24;
@@ -234,7 +235,14 @@ public class IpClientIntegrationTest {
            (byte) 0x00, (byte) 0x17, (byte) 0xF2
    };
    private static final byte TEST_VENDOR_SPECIFIC_TYPE = 0x06;

    private static final String TEST_DEFAULT_SSID = "test_ssid";
    private static final String TEST_DEFAULT_BSSID = "00:11:22:33:44:55";
    private static final String TEST_DHCP_ROAM_SSID = "0001docomo";
    private static final String TEST_DHCP_ROAM_BSSID = "00:4e:35:17:98:55";
    private static final String TEST_DHCP_ROAM_L2KEY = "roaming_l2key";
    private static final String TEST_DHCP_ROAM_GROUPHINT = "roaming_group_hint";
    private static final byte[] TEST_AP_OUI = new byte[] { 0x00, 0x1A, 0x11 };

    private class Dependencies extends IpClient.Dependencies {
        private boolean mIsDhcpLeaseCacheEnabled;
@@ -473,11 +481,11 @@ public class IpClientIntegrationTest {
    }

    private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet,
            final Integer leaseTimeSec, final short mtu, final boolean rapidCommit,
            final String captivePortalApiUrl) {
            final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
            final boolean rapidCommit, final String captivePortalApiUrl) {
        return DhcpPacket.buildAckPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
                false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */,
                CLIENT_ADDR /* yourIp */, CLIENT_ADDR /* requestIp */, packet.getClientMac(),
                clientAddress /* yourIp */, CLIENT_ADDR /* requestIp */, packet.getClientMac(),
                leaseTimeSec, NETMASK /* netMask */, BROADCAST_ADDR /* bcAddr */,
                Collections.singletonList(SERVER_ADDR) /* gateways */,
                Collections.singletonList(SERVER_ADDR) /* dnsServers */,
@@ -524,7 +532,15 @@ public class IpClientIntegrationTest {
        mDependencies.setDhcpRapidCommitEnabled(shouldReplyRapidCommitAck);
        mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled);
        mDependencies.setHostnameConfiguration(isHostnameConfigurationEnabled, hostname);

        if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
            final Layer2InformationParcelable info = new Layer2InformationParcelable();
            info.l2Key = TEST_L2KEY;
            info.groupHint = TEST_GROUPHINT;
            mIpc.updateLayer2Information(info);
        } else {
            mIpc.setL2KeyAndGroupHint(TEST_L2KEY, TEST_GROUPHINT);
        }
        mIpc.startProvisioning(builder.build());
        if (!isPreconnectionEnabled) {
            verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
@@ -616,15 +632,15 @@ public class IpClientIntegrationTest {
            packetList.add(packet);
            if (packet instanceof DhcpDiscoverPacket) {
                if (shouldReplyRapidCommitAck) {
                    mPacketReader.sendResponse(buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu,
                              true /* rapidCommit */, captivePortalApiUrl));
                    mPacketReader.sendResponse(buildDhcpAckPacket(packet, CLIENT_ADDR, leaseTimeSec,
                              (short) mtu, true /* rapidCommit */, captivePortalApiUrl));
                } else {
                    mPacketReader.sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec,
                            (short) mtu, captivePortalApiUrl));
                }
            } else if (packet instanceof DhcpRequestPacket) {
                final ByteBuffer byteBuffer = isSuccessLease
                        ? buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu,
                        ? buildDhcpAckPacket(packet, CLIENT_ADDR, leaseTimeSec, (short) mtu,
                                false /* rapidCommit */, captivePortalApiUrl)
                        : buildDhcpNakPacket(packet);
                mPacketReader.sendResponse(byteBuffer);
@@ -810,8 +826,8 @@ public class IpClientIntegrationTest {
            packet = getNextDhcpPacket();
            assertTrue(packet instanceof DhcpRequestPacket);
        }
        mPacketReader.sendResponse(buildDhcpAckPacket(packet, TEST_LEASE_DURATION_S, mtu,
                shouldReplyRapidCommitAck, null /* captivePortalUrl */));
        mPacketReader.sendResponse(buildDhcpAckPacket(packet, CLIENT_ADDR, TEST_LEASE_DURATION_S,
                mtu, shouldReplyRapidCommitAck, null /* captivePortalUrl */));

        if (!shouldAbortPreconnection) {
            mIpc.notifyPreconnectionComplete(true /* success */);
@@ -1590,9 +1606,15 @@ public class IpClientIntegrationTest {
        return new ScanResultInfo(ssid, bssid, Collections.singletonList(ie));
    }

    private ScanResultInfo makeScanResultInfo(final String ssid, final String bssid) {
        byte[] data = new byte[10];
        new Random().nextBytes(data);
        return makeScanResultInfo(0xdd, ssid, bssid, TEST_AP_OUI, (byte) 0x06, data);
    }

    private void doUpstreamHotspotDetectionTest(final int id, final String displayName,
            final String ssid, final byte[] oui, final byte type, final byte[] data)
            throws Exception {
            final String ssid, final byte[] oui, final byte type, final byte[] data,
            final boolean expectMetered) throws Exception {
        final ScanResultInfo info = makeScanResultInfo(id, ssid, TEST_DEFAULT_BSSID, oui, type,
                data);
        final long currentTime = System.currentTimeMillis();
@@ -1615,10 +1637,8 @@ public class IpClientIntegrationTest {
        assertTrue(lease.getDnsServers().contains(SERVER_ADDR));
        assertEquals(lease.getServerAddress(), SERVER_ADDR);
        assertEquals(lease.getMtu(), TEST_DEFAULT_MTU);
        if (id == VENDOR_SPECIFIC_IE_ID
                && ssid.equals(removeDoubleQuotes(displayName))
                && Arrays.equals(oui, TEST_HOTSPOT_OUI)
                && type == TEST_VENDOR_SPECIFIC_TYPE) {

        if (expectMetered) {
            assertEquals(lease.vendorInfo, DhcpPacket.VENDOR_INFO_ANDROID_METERED);
        } else {
            assertNull(lease.vendorInfo);
@@ -1632,7 +1652,8 @@ public class IpClientIntegrationTest {
        byte[] data = new byte[10];
        new Random().nextBytes(data);
        doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data,
                true /* expectMetered */);
    }

    @Test
@@ -1640,7 +1661,8 @@ public class IpClientIntegrationTest {
        byte[] data = new byte[10];
        new Random().nextBytes(data);
        doUpstreamHotspotDetectionTest(0xdc, "\"ssid\"", "ssid",
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data,
                false /* expectMetered */);
    }

    @Test
@@ -1648,7 +1670,8 @@ public class IpClientIntegrationTest {
        byte[] data = new byte[10];
        new Random().nextBytes(data);
        doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
                new byte[] { (byte) 0x00, (byte) 0x1A, (byte) 0x11 }, (byte) 0x06, data);
                new byte[] { (byte) 0x00, (byte) 0x1A, (byte) 0x11 }, (byte) 0x06, data,
                false /* expectMetered */);
    }

    @Test
@@ -1656,7 +1679,8 @@ public class IpClientIntegrationTest {
        byte[] data = new byte[10];
        new Random().nextBytes(data);
        doUpstreamHotspotDetectionTest(0xdd, "\"another ssid\"", "ssid",
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data,
                false /* expectMetered */);
    }

    @Test
@@ -1664,13 +1688,106 @@ public class IpClientIntegrationTest {
        byte[] data = new byte[10];
        new Random().nextBytes(data);
        doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x0a, data);
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x0a, data,
                false /* expectMetered */);
    }

    @Test
    public void testUpstreamHotspotDetection_zeroLengthData() throws Exception {
        byte[] data = new byte[0];
        doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data);
                new byte[] { (byte) 0x00, (byte) 0x17, (byte) 0xF2 }, (byte) 0x06, data,
                true /* expectMetered */);
    }

    private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName,
            final String ssid, final String bssid, final boolean expectRoaming) throws Exception {
        long currentTime = System.currentTimeMillis();
        final ScanResultInfo scanResultInfo = makeScanResultInfo(ssid, bssid);

        doAnswer(invocation -> {
            // we don't rely on the Init-Reboot state to renew previous cached IP lease.
            // Just return null and force state machine enter INIT state.
            return null;
        }).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any());

        performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
                TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */,
                true /* isHostnameConfigurationEnabled */, null /* hostname */,
                null /* captivePortalApiUrl */, displayName, scanResultInfo);
        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);

        // simulate the roaming by updating bssid.
        final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable();
        roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID);
        roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY;
        roamingInfo.groupHint = TEST_DHCP_ROAM_GROUPHINT;
        mIpc.updateLayer2Information(roamingInfo);

        currentTime = System.currentTimeMillis();
        reset(mIpMemoryStore);
        reset(mCb);
        if (!expectRoaming) {
            assertIpMemoryNeverStoreNetworkAttributes();
            return;
        }
        // check DHCPREQUEST broadcast sent to renew IP address.
        DhcpPacket packet;
        packet = getNextDhcpPacket();
        assertTrue(packet instanceof DhcpRequestPacket);
        assertEquals(packet.mClientIp, CLIENT_ADDR);    // client IP
        assertNull(packet.mRequestedIp);                // requested IP option
        assertNull(packet.mServerIdentifier);           // server ID

        mPacketReader.sendResponse(buildDhcpAckPacket(packet,
                hasMismatchedIpAddress ? CLIENT_ADDR_NEW : CLIENT_ADDR, TEST_LEASE_DURATION_S,
                (short) TEST_DEFAULT_MTU, false /* rapidcommit */, null /* captivePortalUrl */));
        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
        if (hasMismatchedIpAddress) {
            // notifyFailure
            ArgumentCaptor<DhcpResultsParcelable> captor =
                    ArgumentCaptor.forClass(DhcpResultsParcelable.class);
            verify(mCb, timeout(TEST_TIMEOUT_MS)).onNewDhcpResults(captor.capture());
            DhcpResults lease = fromStableParcelable(captor.getValue());
            assertNull(lease);

            // roll back to INIT state.
            packet = getNextDhcpPacket();
            assertTrue(packet instanceof DhcpDiscoverPacket);
        } else {
            assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime,
                    TEST_DEFAULT_MTU);
        }
    }

    @Test
    public void testDhcpRoaming() throws Exception {
        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
    }

    @Test
    public void testDhcpRoaming_invalidBssid() throws Exception {
        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                TEST_DHCP_ROAM_SSID, TEST_DHCP_ROAM_BSSID, false /* expectRoaming */);
    }

    @Test
    public void testDhcpRoaming_invalidSsid() throws Exception {
        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
    }

    @Test
    public void testDhcpRoaming_invalidDisplayName() throws Exception {
        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"test-ssid\"" /* display name */,
                TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
    }

    @Test
    public void testDhcpRoaming_mismatchedLeasedIpAddress() throws Exception {
        doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
    }
}