Loading src/android/net/dhcp/DhcpClient.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading src/android/net/dhcp/DhcpPacket.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading src/android/net/ip/IpClient.java +68 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(); Loading Loading @@ -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 Loading Loading @@ -704,7 +716,6 @@ public class IpClient extends StateMachine { enforceNetworkStackCallingPermission(); IpClient.this.notifyPreconnectionComplete(success); } @Override public void updateLayer2Information(Layer2InformationParcelable info) { enforceNetworkStackCallingPermission(); Loading Loading @@ -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)); } Loading Loading @@ -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. Loading Loading @@ -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); } /** Loading Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -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; Loading tests/integration/src/android/net/ip/IpClientIntegrationTest.java +140 −23 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 */, Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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 */); Loading Loading @@ -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(); Loading @@ -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); Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 */); } } Loading
src/android/net/dhcp/DhcpClient.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading
src/android/net/dhcp/DhcpPacket.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading
src/android/net/ip/IpClient.java +68 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(); Loading Loading @@ -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 Loading Loading @@ -704,7 +716,6 @@ public class IpClient extends StateMachine { enforceNetworkStackCallingPermission(); IpClient.this.notifyPreconnectionComplete(success); } @Override public void updateLayer2Information(Layer2InformationParcelable info) { enforceNetworkStackCallingPermission(); Loading Loading @@ -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)); } Loading Loading @@ -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. Loading Loading @@ -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); } /** Loading Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -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; Loading
tests/integration/src/android/net/ip/IpClientIntegrationTest.java +140 −23 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 */, Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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 */); Loading Loading @@ -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(); Loading @@ -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); Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 */); } }