diff options
-rw-r--r-- | apishim/30/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java | 2 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpClient.java | 32 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpPacket.java | 64 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpServer.java | 7 | ||||
-rw-r--r-- | src/android/net/ip/IpClient.java | 11 | ||||
-rw-r--r-- | src/android/net/util/ConnectivityPacketSummary.java | 14 | ||||
-rw-r--r-- | tests/integration/Android.bp | 30 | ||||
-rw-r--r-- | tests/integration/src/android/net/ip/IpClientIntegrationTest.java | 100 | ||||
-rw-r--r-- | tests/unit/src/android/net/dhcp/DhcpPacketTest.java | 153 | ||||
-rw-r--r-- | tests/unit/src/android/net/dhcp/DhcpServerTest.java | 3 |
10 files changed, 347 insertions, 69 deletions
diff --git a/apishim/30/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java index 2df0b38..bee43b4 100644 --- a/apishim/30/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java +++ b/apishim/30/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java @@ -23,7 +23,6 @@ import android.os.Build; import android.os.RemoteException; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import org.json.JSONException; import org.json.JSONObject; @@ -70,7 +69,6 @@ public class CaptivePortalDataShimImpl .build()); } - @VisibleForTesting public static boolean isSupported() { return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); } diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index fbcd0f6..41e54a4 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java @@ -17,6 +17,7 @@ package android.net.dhcp; import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; +import static android.net.dhcp.DhcpPacket.DHCP_CAPTIVE_PORTAL; import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; @@ -95,6 +96,7 @@ import com.android.internal.util.StateMachine; import com.android.internal.util.TrafficStatsConstants; import com.android.internal.util.WakeupMessage; import com.android.networkstack.R; +import com.android.networkstack.apishim.CaptivePortalDataShimImpl; import com.android.networkstack.apishim.SocketUtilsShimImpl; import com.android.networkstack.arp.ArpPacket; @@ -260,8 +262,9 @@ public class DhcpClient extends StateMachine { private static final SparseArray<String> sMessageNames = MessageUtils.findMessageNames(sMessageClasses); - // DHCP parameters that we request. - /* package */ static final byte[] REQUESTED_PARAMS = new byte[] { + // DHCP parameters that we request by default. + @VisibleForTesting + /* package */ static final byte[] DEFAULT_REQUESTED_PARAMS = new byte[] { DHCP_SUBNET_MASK, DHCP_ROUTER, DHCP_DNS_SERVER, @@ -274,6 +277,21 @@ public class DhcpClient extends StateMachine { DHCP_VENDOR_INFO, }; + @NonNull + private byte[] getRequestedParams() { + if (isCapportApiEnabled()) { + final byte[] params = Arrays.copyOf(DEFAULT_REQUESTED_PARAMS, + DEFAULT_REQUESTED_PARAMS.length + 1); + params[params.length - 1] = DHCP_CAPTIVE_PORTAL; + return params; + } + return DEFAULT_REQUESTED_PARAMS; + } + + private static boolean isCapportApiEnabled() { + return CaptivePortalDataShimImpl.isSupported(); + } + // DHCP flag that means "yes, we support unicast." private static final boolean DO_UNICAST = false; @@ -556,8 +574,10 @@ public class DhcpClient extends StateMachine { @Override protected void handlePacket(byte[] recvbuf, int length) { try { + final byte[] optionsToSkip = + isCapportApiEnabled() ? new byte[0] : new byte[] { DHCP_CAPTIVE_PORTAL }; final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf, length, - DhcpPacket.ENCAP_L2); + DhcpPacket.ENCAP_L2, optionsToSkip); if (DBG) Log.d(TAG, "Received packet: " + packet); sendMessage(CMD_RECEIVED_PACKET, packet); } catch (DhcpPacket.ParseException e) { @@ -649,7 +669,7 @@ public class DhcpClient extends StateMachine { private boolean sendDiscoverPacket() { final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS, isDhcpRapidCommitEnabled(), mHostname); + DO_UNICAST, getRequestedParams(), isDhcpRapidCommitEnabled(), mHostname); return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); } @@ -663,7 +683,7 @@ public class DhcpClient extends StateMachine { final ByteBuffer packet = DhcpPacket.buildRequestPacket( encap, mTransactionId, getSecs(), clientAddress, DO_UNICAST, mHwAddr, requestedAddress, - serverAddress, REQUESTED_PARAMS, mHostname); + serverAddress, getRequestedParams(), mHostname); String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + " request=" + requestedAddress.getHostAddress() + @@ -1267,7 +1287,7 @@ public class DhcpClient extends StateMachine { final Layer2PacketParcelable l2Packet = new Layer2PacketParcelable(); final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS, true /* rapid commit */, mHostname); + DO_UNICAST, getRequestedParams(), true /* rapid commit */, mHostname); l2Packet.dstMacAddress = MacAddress.fromBytes(DhcpPacket.ETHER_BROADCAST); l2Packet.payload = Arrays.copyOf(packet.array(), packet.limit()); diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java index 9eca0b5..19af74c 100644 --- a/src/android/net/dhcp/DhcpPacket.java +++ b/src/android/net/dhcp/DhcpPacket.java @@ -307,6 +307,10 @@ public abstract class DhcpPacket { protected static final byte DHCP_RAPID_COMMIT = 80; protected boolean mRapidCommit; + @VisibleForTesting + public static final byte DHCP_CAPTIVE_PORTAL = (byte) 114; + protected String mCaptivePortalUrl; + /** * DHCP zero-length option code: pad */ @@ -785,6 +789,7 @@ public abstract class DhcpPacket { if (mMtu != null && Short.toUnsignedInt(mMtu) >= IPV4_MIN_MTU) { addTlv(buf, DHCP_MTU, mMtu); } + addTlv(buf, DHCP_CAPTIVE_PORTAL, mCaptivePortalUrl); } /** @@ -871,6 +876,23 @@ public abstract class DhcpPacket { } } + private static int skipOption(ByteBuffer packet, int optionLen) + throws BufferUnderflowException { + int expectedLen = 0; + for (int i = 0; i < optionLen; i++) { + expectedLen++; + packet.get(); + } + return expectedLen; + } + + private static boolean shouldSkipOption(byte optionType, byte[] optionsToSkip) { + for (byte option : optionsToSkip) { + if (option == optionType) return true; + } + return false; + } + /** * Creates a concrete DhcpPacket from the supplied ByteBuffer. The * buffer may have an L2 encapsulation (which is the full EthernetII @@ -881,8 +903,8 @@ public abstract class DhcpPacket { * in object fields. */ @VisibleForTesting - static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException - { + static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType, byte[] optionsToSkip) + throws ParseException { // bootp parameters int transactionId; short secs; @@ -900,6 +922,7 @@ public abstract class DhcpPacket { String vendorId = null; String vendorInfo = null; boolean rapidCommit = false; + String captivePortalUrl = null; byte[] expectedParams = null; String hostName = null; String domainName = null; @@ -1080,6 +1103,11 @@ public abstract class DhcpPacket { int optionLen = packet.get() & 0xFF; int expectedLen = 0; + if (shouldSkipOption(optionType, optionsToSkip)) { + skipOption(packet, optionLen); + continue; + } + switch(optionType) { case DHCP_SUBNET_MASK: netMask = readIpAddress(packet); @@ -1172,12 +1200,12 @@ public abstract class DhcpPacket { expectedLen = 0; rapidCommit = true; break; + case DHCP_CAPTIVE_PORTAL: + expectedLen = optionLen; + captivePortalUrl = readAsciiString(packet, optionLen, true); + break; default: - // ignore any other parameters - for (int i = 0; i < optionLen; i++) { - expectedLen++; - byte throwaway = packet.get(); - } + expectedLen = skipOption(packet, optionLen); } if (expectedLen != optionLen) { @@ -1263,6 +1291,7 @@ public abstract class DhcpPacket { newPacket.mT2 = T2; newPacket.mVendorId = vendorId; newPacket.mVendorInfo = vendorInfo; + newPacket.mCaptivePortalUrl = captivePortalUrl; if ((optionOverload & OPTION_OVERLOAD_SNAME) == 0) { newPacket.mServerHostName = serverHostName; } else { @@ -1274,11 +1303,11 @@ public abstract class DhcpPacket { /** * Parse a packet from an array of bytes, stopping at the given length. */ - public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType) - throws ParseException { + public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType, + byte[] optionsToSkip) throws ParseException { ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN); try { - return decodeFullPacket(buffer, pktType); + return decodeFullPacket(buffer, pktType, optionsToSkip); } catch (ParseException e) { throw e; } catch (Exception e) { @@ -1287,6 +1316,14 @@ public abstract class DhcpPacket { } /** + * Parse a packet from an array of bytes, stopping at the given length. + */ + public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType) + throws ParseException { + return decodeFullPacket(packet, length, pktType, new byte[0]); + } + + /** * Construct a DhcpResults object from a DHCP reply packet. */ public DhcpResults toDhcpResults() { @@ -1328,6 +1365,7 @@ public abstract class DhcpPacket { results.leaseDuration = (mLeaseTime != null) ? mLeaseTime : INFINITE_LEASE; results.mtu = (mMtu != null && MIN_MTU <= mMtu && mMtu <= MAX_MTU) ? mMtu : 0; results.serverHostName = mServerHostName; + results.captivePortalApiUrl = mCaptivePortalUrl; return results; } @@ -1369,7 +1407,7 @@ public abstract class DhcpPacket { Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered, - short mtu) { + short mtu, String captivePortalUrl) { DhcpPacket pkt = new DhcpOfferPacket( transactionId, (short) 0, broadcast, serverIpAddr, relayIp, INADDR_ANY /* clientIp */, yourIp, mac); @@ -1382,6 +1420,7 @@ public abstract class DhcpPacket { pkt.mSubnetMask = netMask; pkt.mBroadcastAddress = bcAddr; pkt.mMtu = mtu; + pkt.mCaptivePortalUrl = captivePortalUrl; if (metered) { pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED; } @@ -1396,7 +1435,7 @@ public abstract class DhcpPacket { Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered, - short mtu, boolean rapidCommit) { + short mtu, boolean rapidCommit, String captivePortalUrl) { DhcpPacket pkt = new DhcpAckPacket( transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp, mac, rapidCommit); @@ -1409,6 +1448,7 @@ public abstract class DhcpPacket { pkt.mServerIdentifier = dhcpServerIdentifier; pkt.mBroadcastAddress = bcAddr; pkt.mMtu = mtu; + pkt.mCaptivePortalUrl = captivePortalUrl; if (metered) { pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED; } diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java index 9e54a69..9c5b3c6 100644 --- a/src/android/net/dhcp/DhcpServer.java +++ b/src/android/net/dhcp/DhcpServer.java @@ -529,7 +529,9 @@ public class DhcpServer extends IDhcpServer.Stub { broadcastAddr, new ArrayList<>(mServingParams.defaultRouters), new ArrayList<>(mServingParams.dnsServers), mServingParams.getServerInet4Addr(), null /* domainName */, hostname, - mServingParams.metered, (short) mServingParams.linkMtu); + mServingParams.metered, (short) mServingParams.linkMtu, + // TODO (b/144402437): advertise the URL if known + null /* captivePortalApiUrl */); return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag); } @@ -549,7 +551,8 @@ public class DhcpServer extends IDhcpServer.Stub { new ArrayList<>(mServingParams.dnsServers), mServingParams.getServerInet4Addr(), null /* domainName */, hostname, mServingParams.metered, (short) mServingParams.linkMtu, - packet.mRapidCommit && mDhcpRapidCommitEnabled); + // TODO (b/144402437): advertise the URL if known + packet.mRapidCommit && mDhcpRapidCommitEnabled, null /* captivePortalApiUrl */); return transmitOfferOrAckPacket(ackPacket, packet, lease, clientMac, broadcastFlag); } diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 47f955a..173f136 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java @@ -36,6 +36,7 @@ import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.TcpKeepalivePacketDataParcelable; +import android.net.Uri; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.dhcp.DhcpClient; @@ -57,6 +58,7 @@ import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; import android.util.Pair; +import android.util.Patterns; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -1192,6 +1194,15 @@ public class IpClient extends StateMachine { if (mDhcpResults.mtu != 0) { newLp.setMtu(mDhcpResults.mtu); } + + final String capportUrl = mDhcpResults.captivePortalApiUrl; + // Uri.parse does no syntax check; do a simple regex check to eliminate garbage. + // If the URL is still incorrect data fetching will fail later, which is fine. + if (capportUrl != null && Patterns.WEB_URL.matcher(capportUrl).matches()) { + NetworkInformationShimImpl.newInstance() + .setCaptivePortalApiUrl(newLp, Uri.parse(capportUrl)); + } + // TODO: also look at the IPv6 RA (netlink) for captive portal URL } // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. diff --git a/src/android/net/util/ConnectivityPacketSummary.java b/src/android/net/util/ConnectivityPacketSummary.java index 08c3f60..4d04911 100644 --- a/src/android/net/util/ConnectivityPacketSummary.java +++ b/src/android/net/util/ConnectivityPacketSummary.java @@ -82,12 +82,26 @@ public class ConnectivityPacketSummary { private final ByteBuffer mPacket; private final String mSummary; + /** + * Create a string summary of a received packet. + * @param hwaddr MacAddress of the receiving device. + * @param buffer Buffer of the packet. Length is assumed to be the buffer length. + * @return A summary of the packet. + */ public static String summarize(MacAddress hwaddr, byte[] buffer) { return summarize(hwaddr, buffer, buffer.length); } // Methods called herein perform some but by no means all error checking. // They may throw runtime exceptions on malformed packets. + + /** + * Create a string summary of a received packet. + * @param macAddr MacAddress of the receiving device. + * @param buffer Buffer of the packet. + * @param length Length of the packet. + * @return A summary of the packet. + */ public static String summarize(MacAddress macAddr, byte[] buffer, int length) { if ((macAddr == null) || (buffer == null)) return null; length = Math.min(length, buffer.length); diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index c4f057b..a9eabc8 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp @@ -27,9 +27,8 @@ java_defaults { visibility: ["//visibility:private"], } -android_library { - name: "NetworkStackIntegrationTestsLib", - min_sdk_version: "29", +java_defaults { + name: "NetworkStackIntegrationTestsDefaults", srcs: ["src/**/*.java"], static_libs: [ "androidx.annotation_annotation", @@ -37,7 +36,6 @@ android_library { "mockito-target-extended-minus-junit4", "net-tests-utils", "testables", - "NetworkStackApiStableLib", ], libs: [ "android.test.runner", @@ -48,6 +46,15 @@ android_library { visibility: ["//visibility:private"], } +android_library { + name: "NetworkStackIntegrationTestsLib", + defaults: ["NetworkStackIntegrationTestsDefaults"], + min_sdk_version: "29", + static_libs: [ + "NetworkStackApiStableLib", + ], +} + // Network stack integration tests. android_test { name: "NetworkStackIntegrationTests", @@ -59,6 +66,21 @@ android_test { min_sdk_version: "29", } +// Network stack next integration tests. +android_test { + name: "NetworkStackNextIntegrationTests", + defaults: [ + "NetworkStackIntegrationTestsDefaults", + "NetworkStackIntegrationTestsJniDefaults", + ], + static_libs: [ + "NetworkStackApiCurrentLib", + ], + certificate: "networkstack", + platform_apis: true, + test_suites: ["device-tests"], +} + // Special version of the network stack tests that includes all tests necessary for code coverage // purposes. This is currently the union of NetworkStackTests and NetworkStackIntegrationTests. android_test { diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java index a6543bc..a60e32c 100644 --- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java @@ -57,6 +57,8 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; @@ -87,6 +89,7 @@ import android.net.MacAddress; import android.net.NetworkStackIpMemoryStore; import android.net.TestNetworkInterface; import android.net.TestNetworkManager; +import android.net.Uri; import android.net.dhcp.DhcpClient; import android.net.dhcp.DhcpDeclinePacket; import android.net.dhcp.DhcpDiscoverPacket; @@ -118,6 +121,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.StateMachine; +import com.android.networkstack.apishim.CaptivePortalDataShimImpl; import com.android.networkstack.apishim.ShimUtils; import com.android.networkstack.arp.ArpPacket; import com.android.server.NetworkObserverRegistry; @@ -147,6 +151,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -220,6 +225,7 @@ public class IpClientIntegrationTest { private static final byte[] SERVER_MAC = new byte[] { 0x00, 0x1A, 0x11, 0x22, 0x33, 0x44 }; private static final String TEST_HOST_NAME = "AOSP on Crosshatch"; private static final String TEST_HOST_NAME_TRANSLITERATION = "AOSP-on-Crosshatch"; + private static final String TEST_CAPTIVE_PORTAL_URL = "https://example.com/capportapi"; private static class TapPacketReader extends PacketReader { private final ParcelFileDescriptor mTapFd; @@ -485,7 +491,7 @@ public class IpClientIntegrationTest { } private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet, - final Integer leaseTimeSec, final short mtu) { + final Integer leaseTimeSec, final short mtu, final String captivePortalUrl) { return DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(), false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */, CLIENT_ADDR /* yourIp */, packet.getClientMac(), leaseTimeSec, @@ -493,11 +499,12 @@ public class IpClientIntegrationTest { Collections.singletonList(SERVER_ADDR) /* gateways */, Collections.singletonList(SERVER_ADDR) /* dnsServers */, SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME, - false /* metered */, mtu); + false /* metered */, mtu, captivePortalUrl); } private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet, - final Integer leaseTimeSec, final short mtu, final boolean rapidCommit) { + 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(), @@ -505,7 +512,7 @@ public class IpClientIntegrationTest { Collections.singletonList(SERVER_ADDR) /* gateways */, Collections.singletonList(SERVER_ADDR) /* dnsServers */, SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME, - false /* metered */, mtu, rapidCommit); + false /* metered */, mtu, rapidCommit, captivePortalApiUrl); } private static ByteBuffer buildDhcpNakPacket(final DhcpPacket packet) { @@ -625,27 +632,35 @@ public class IpClientIntegrationTest { final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled, final boolean shouldReplyRapidCommitAck, final int mtu, final boolean isDhcpIpConflictDetectEnabled, - final boolean isHostnameConfigurationEnabled, final String hostname) - throws Exception { - final List<DhcpPacket> packetList = new ArrayList<DhcpPacket>(); + final boolean isHostnameConfigurationEnabled, final String hostname, + final String captivePortalApiUrl) throws Exception { startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck, false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled, isHostnameConfigurationEnabled, hostname); + return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu, + isDhcpIpConflictDetectEnabled, captivePortalApiUrl); + } + private List<DhcpPacket> handleDhcpPackets(final boolean isSuccessLease, + final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu, + final boolean isDhcpIpConflictDetectEnabled, final String captivePortalApiUrl) + throws Exception { + final List<DhcpPacket> packetList = new ArrayList<>(); DhcpPacket packet; while ((packet = getNextDhcpPacket()) != null) { packetList.add(packet); if (packet instanceof DhcpDiscoverPacket) { if (shouldReplyRapidCommitAck) { sendResponse(buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu, - true /* rapidCommit */)); + true /* rapidCommit */, captivePortalApiUrl)); } else { - sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec, (short) mtu)); + sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec, (short) mtu, + captivePortalApiUrl)); } } else if (packet instanceof DhcpRequestPacket) { final ByteBuffer byteBuffer = isSuccessLease ? buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu, - false /* rapidCommit */) + false /* rapidCommit */, captivePortalApiUrl) : buildDhcpNakPacket(packet); sendResponse(byteBuffer); } else { @@ -677,7 +692,8 @@ public class IpClientIntegrationTest { final boolean isDhcpIpConflictDetectEnabled) throws Exception { return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled, - false /* isHostnameConfigurationEnabled */, null /* hostname */); + false /* isHostnameConfigurationEnabled */, null /* hostname */, + null /* captivePortalApiUrl */); } private DhcpPacket getNextDhcpPacket() throws ParseException { @@ -813,12 +829,13 @@ public class IpClientIntegrationTest { final short mtu = (short) TEST_DEFAULT_MTU; if (!shouldReplyRapidCommitAck) { - sendResponse(buildDhcpOfferPacket(packet, TEST_LEASE_DURATION_S, mtu)); + sendResponse(buildDhcpOfferPacket(packet, TEST_LEASE_DURATION_S, mtu, + null /* captivePortalUrl */)); packet = getNextDhcpPacket(); assertTrue(packet instanceof DhcpRequestPacket); } sendResponse(buildDhcpAckPacket(packet, TEST_LEASE_DURATION_S, mtu, - shouldReplyRapidCommitAck)); + shouldReplyRapidCommitAck, null /* captivePortalUrl */)); if (!shouldAbortPreconnection) { mIpc.notifyPreconnectionComplete(true /* success */); @@ -1466,7 +1483,8 @@ public class IpClientIntegrationTest { TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, - true /* isHostnameConfigurationEnabled */, TEST_HOST_NAME /* hostname */); + true /* isHostnameConfigurationEnabled */, TEST_HOST_NAME /* hostname */, + null /* captivePortalApiUrl */); assertEquals(2, sentPackets.size()); assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); @@ -1479,7 +1497,8 @@ public class IpClientIntegrationTest { TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, - false /* isHostnameConfigurationEnabled */, TEST_HOST_NAME); + false /* isHostnameConfigurationEnabled */, TEST_HOST_NAME, + null /* captivePortalApiUrl */); assertEquals(2, sentPackets.size()); assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); @@ -1492,10 +1511,59 @@ public class IpClientIntegrationTest { TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, - true /* isHostnameConfigurationEnabled */, null /* hostname */); + true /* isHostnameConfigurationEnabled */, null /* hostname */, + null /* captivePortalApiUrl */); assertEquals(2, sentPackets.size()); assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */, sentPackets); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); } + + private void runDhcpClientCaptivePortalApiTest(boolean featureEnabled, + boolean serverSendsOption) throws Exception { + startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, + false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */, + false /* isDhcpIpConflictDetectEnabled */); + final DhcpPacket discover = getNextDhcpPacket(); + assertTrue(discover instanceof DhcpDiscoverPacket); + assertEquals(featureEnabled, discover.hasRequestedParam(DhcpPacket.DHCP_CAPTIVE_PORTAL)); + + // Send Offer and handle Request -> Ack + final String serverSentUrl = serverSendsOption ? TEST_CAPTIVE_PORTAL_URL : null; + sendResponse(buildDhcpOfferPacket(discover, TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, + serverSentUrl)); + final int testMtu = 1345; + handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S, + false /* isDhcpRapidCommitEnabled */, testMtu, + false /* isDhcpIpConflictDetectEnabled */, serverSentUrl); + + final Uri expectedUrl = featureEnabled && serverSendsOption + ? Uri.parse(TEST_CAPTIVE_PORTAL_URL) : null; + // Wait for LinkProperties containing DHCP-obtained info, such as MTU, and ensure that the + // URL is set as expected + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat(lp -> + lp.getMtu() == testMtu + && Objects.equals(expectedUrl, lp.getCaptivePortalApiUrl()))); + } + + @Test + public void testDhcpClientCaptivePortalApiEnabled() throws Exception { + // Only run the test on platforms / builds where the API is enabled + assumeTrue(CaptivePortalDataShimImpl.isSupported()); + runDhcpClientCaptivePortalApiTest(true /* featureEnabled */, true /* serverSendsOption */); + } + + @Test + public void testDhcpClientCaptivePortalApiEnabled_NoUrl() throws Exception { + // Only run the test on platforms / builds where the API is enabled + assumeTrue(CaptivePortalDataShimImpl.isSupported()); + runDhcpClientCaptivePortalApiTest(true /* featureEnabled */, false /* serverSendsOption */); + } + + @Test + public void testDhcpClientCaptivePortalApiDisabled() throws Exception { + // Only run the test on platforms / builds where the API is disabled + assumeFalse(CaptivePortalDataShimImpl.isSupported()); + runDhcpClientCaptivePortalApiTest(false /* featureEnabled */, true /* serverSendsOption */); + } } diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java index 090631b..6ce1fdf 100644 --- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java +++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java @@ -78,10 +78,12 @@ public class DhcpPacketTest { private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress( SERVER_ADDR, PREFIX_LENGTH); private static final String HOSTNAME = "testhostname"; + private static final String CAPTIVE_PORTAL_API_URL = "https://example.com/capportapi"; private static final short MTU = 1500; // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code // doesn't use == instead of equals when comparing addresses. private static final Inet4Address ANY = v4Address("0.0.0.0"); + private static final byte[] TEST_EMPTY_OPTIONS_SKIP_LIST = new byte[0]; private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; @@ -169,7 +171,8 @@ public class DhcpPacketTest { .setDomainBytes(domainBytes) .setVendorInfoBytes(vendorInfoBytes) .build(); - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertEquals(expectedDomain, offerPacket.mDomainName); assertEquals(expectedVendorInfo, offerPacket.mVendorInfo); } @@ -215,14 +218,16 @@ public class DhcpPacketTest { if (!expectValid) { try { - offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, + TEST_EMPTY_OPTIONS_SKIP_LIST); fail("Invalid packet parsed successfully: " + offerPacket); } catch (ParseException expected) { } return; } - offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertNotNull(offerPacket); assertEquals(rawLeaseTime, offerPacket.mLeaseTime); DhcpResults dhcpResults = offerPacket.toDhcpResults(); // Just check this doesn't crash. @@ -266,7 +271,8 @@ public class DhcpPacketTest { ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp) .setNetmaskBytes(netmaskBytes) .build(); - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, + TEST_EMPTY_OPTIONS_SKIP_LIST); DhcpResults results = offerPacket.toDhcpResults(); if (expected != null) { @@ -351,7 +357,8 @@ public class DhcpPacketTest { "3a0400000e103b040000189cff00000000000000000000")); // CHECKSTYLE:ON Generated code - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. DhcpResults dhcpResults = offerPacket.toDhcpResults(); assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", @@ -384,7 +391,8 @@ public class DhcpPacketTest { // CHECKSTYLE:ON Generated code assertEquals(337, packet.limit()); - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. DhcpResults dhcpResults = offerPacket.toDhcpResults(); assertDhcpResults("192.168.43.247/24", "192.168.43.1", "192.168.43.1", @@ -393,6 +401,88 @@ public class DhcpPacketTest { assertTrue(dhcpResults.hasMeteredHint()); } + private void runCapportOptionTest(boolean enabled) throws Exception { + // CHECKSTYLE:OFF Generated code + final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( + // IP header. + "450001518d0600004011144dc0a82b01c0a82bf7" + + // UDP header + "00430044013d9ac7" + + // BOOTP header + "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + + // MAC address. + "30766ff2a90c00000000000000000000" + + // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator). + "646863702e616e64726f69642e636f6d00000000000000000000000000000000" + + "0000000000004141414100000000000000000000000000000000000000000000" + + // File. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // Options + "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + + "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544721d" + + "68747470733a2f2f706f7274616c6170692e6578616d706c652e636f6dff")); + // CHECKSTYLE:ON Generated code + + final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + enabled ? TEST_EMPTY_OPTIONS_SKIP_LIST + : new byte[] { DhcpPacket.DHCP_CAPTIVE_PORTAL }); + assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. + final DhcpResults dhcpResults = offerPacket.toDhcpResults(); + final String testUrl = enabled ? "https://portalapi.example.com" : null; + assertEquals(testUrl, dhcpResults.captivePortalApiUrl); + } + + @Test + public void testCapportOption() throws Exception { + runCapportOptionTest(true /* enabled */); + } + + @Test + public void testCapportOption_Disabled() throws Exception { + runCapportOptionTest(false /* enabled */); + } + + @Test + public void testCapportOption_Invalid() throws Exception { + // CHECKSTYLE:OFF Generated code + final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( + // IP header. + "450001518d0600004011144dc0a82b01c0a82bf7" + + // UDP header + "00430044013d9ac7" + + // BOOTP header + "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + + // MAC address. + "30766ff2a90c00000000000000000000" + + // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator). + "646863702e616e64726f69642e636f6d00000000000000000000000000000000" + + "0000000000004141414100000000000000000000000000000000000000000000" + + // File. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // Options + "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + + "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544" + + // Option 114 (0x72, capport), length 10 (0x0a) + "720a" + + // バグ-com in UTF-8, plus the ff byte that marks the end of options. + "e38390e382b02d636f6dff")); + // CHECKSTYLE:ON Generated code + + final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); + assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. + final DhcpResults dhcpResults = offerPacket.toDhcpResults(); + // Output URL will be garbled because some characters do not exist in the target charset, + // but the parser should not crash. + assertTrue(dhcpResults.captivePortalApiUrl.length() > 0); + } + @Test public void testBadIpPacket() throws Exception { final byte[] packet = HexDump.hexStringToByteArray( @@ -400,7 +490,8 @@ public class DhcpPacketTest { "450001518d0600004011144dc0a82b01c0a82bf7"); try { - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); + DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); } catch (DhcpPacket.ParseException expected) { assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); return; @@ -419,7 +510,7 @@ public class DhcpPacketTest { "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000"); try { - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); + DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); } catch (DhcpPacket.ParseException expected) { assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); return; @@ -448,7 +539,7 @@ public class DhcpPacketTest { "00000000000000000000000000000000000000000000000000000000000000"); try { - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); + DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); } catch (DhcpPacket.ParseException expected) { assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); return; @@ -479,7 +570,7 @@ public class DhcpPacketTest { ); try { - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); + DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); } catch (DhcpPacket.ParseException expected) { assertDhcpErrorCodes(DhcpErrorEvent.DHCP_NO_COOKIE, expected.errorCode); return; @@ -511,7 +602,7 @@ public class DhcpPacketTest { "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"); try { - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); + DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); } catch (DhcpPacket.ParseException expected) { assertDhcpErrorCodes(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE, expected.errorCode); return; @@ -590,7 +681,8 @@ public class DhcpPacketTest { packet.put(mtuBytes); packet.clear(); } - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. DhcpResults dhcpResults = offerPacket.toDhcpResults(); assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", @@ -665,7 +757,8 @@ public class DhcpPacketTest { assertEquals(6, packet.get(hwAddrLenOffset)); // Expect the expected. - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertNotNull(offerPacket); assertEquals(6, offerPacket.getClientMac().length); assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); @@ -673,7 +766,7 @@ public class DhcpPacketTest { // Reduce the hardware address length and verify that it shortens the client MAC. packet.flip(); packet.put(hwAddrLenOffset, (byte) 5); - offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); assertNotNull(offerPacket); assertEquals(5, offerPacket.getClientMac().length); assertEquals(expectedClientMac.substring(0, 10), @@ -681,7 +774,7 @@ public class DhcpPacketTest { packet.flip(); packet.put(hwAddrLenOffset, (byte) 3); - offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); assertNotNull(offerPacket); assertEquals(3, offerPacket.getClientMac().length); assertEquals(expectedClientMac.substring(0, 6), @@ -691,7 +784,7 @@ public class DhcpPacketTest { // and crash, and b) hardcode it to 6. packet.flip(); packet.put(hwAddrLenOffset, (byte) -1); - offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); assertNotNull(offerPacket); assertEquals(6, offerPacket.getClientMac().length); assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); @@ -700,7 +793,7 @@ public class DhcpPacketTest { // hardcode it to 6. packet.flip(); packet.put(hwAddrLenOffset, (byte) 17); - offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); assertNotNull(offerPacket); assertEquals(6, offerPacket.getClientMac().length); assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); @@ -740,7 +833,8 @@ public class DhcpPacketTest { "0000000000000000000000000000000000000000000000ff000000")); // CHECKSTYLE:ON Generated code - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); DhcpResults dhcpResults = offerPacket.toDhcpResults(); assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1", @@ -772,7 +866,8 @@ public class DhcpPacketTest { "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000")); // CHECKSTYLE:ON Generated code - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); DhcpResults dhcpResults = offerPacket.toDhcpResults(); assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2", @@ -806,7 +901,8 @@ public class DhcpPacketTest { "0f0b6c616e63732e61632e756b000000000000000000ff00000000")); // CHECKSTYLE:ON Generated code - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac())); DhcpResults dhcpResults = offerPacket.toDhcpResults(); @@ -842,7 +938,8 @@ public class DhcpPacketTest { "d18180060f0777766d2e6564751c040a0fffffff000000")); // CHECKSTYLE:ON Generated code - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac())); DhcpResults dhcpResults = offerPacket.toDhcpResults(); @@ -880,7 +977,7 @@ public class DhcpPacketTest { // CHECKSTYLE:ON Generated code try { - DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + DhcpPacket.decodeFullPacket(packet, ENCAP_L2, TEST_EMPTY_OPTIONS_SKIP_LIST); fail("Packet with invalid dst port did not throw ParseException"); } catch (ParseException expected) {} } @@ -912,7 +1009,8 @@ public class DhcpPacketTest { "0308c0a8bd01ffffff0006080808080808080404ff000000000000")); // CHECKSTYLE:ON Generated code - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, + TEST_EMPTY_OPTIONS_SKIP_LIST); assertTrue(offerPacket instanceof DhcpOfferPacket); assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac())); DhcpResults dhcpResults = offerPacket.toDhcpResults(); @@ -931,8 +1029,8 @@ public class DhcpPacketTest { ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr, - false /* do unicast */, DhcpClient.REQUESTED_PARAMS, false /* rapid commit */, - testHostname); + false /* do unicast */, DhcpClient.DEFAULT_REQUESTED_PARAMS, + false /* rapid commit */, testHostname); final byte[] headers = new byte[] { // Ethernet header. @@ -1021,7 +1119,7 @@ public class DhcpPacketTest { BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */, Collections.singletonList(SERVER_ADDR) /* dnsServers */, SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, hostname, - false /* metered */, MTU); + false /* metered */, MTU, CAPTIVE_PORTAL_API_URL); ByteArrayOutputStream bos = new ByteArrayOutputStream(); // BOOTP headers @@ -1085,6 +1183,9 @@ public class DhcpPacketTest { // MTU bos.write(new byte[] { (byte) 0x1a, (byte) 0x02 }); bos.write(shortToByteArray(MTU)); + // capport URL. Option 114 = 0x72 + bos.write(new byte[] { (byte) 0x72, (byte) CAPTIVE_PORTAL_API_URL.length() }); + bos.write(CAPTIVE_PORTAL_API_URL.getBytes(Charset.forName("US-ASCII"))); // End options. bos.write(0xff); diff --git a/tests/unit/src/android/net/dhcp/DhcpServerTest.java b/tests/unit/src/android/net/dhcp/DhcpServerTest.java index a1613c5..a278a88 100644 --- a/tests/unit/src/android/net/dhcp/DhcpServerTest.java +++ b/tests/unit/src/android/net/dhcp/DhcpServerTest.java @@ -383,7 +383,8 @@ public class DhcpServerTest { private DhcpPacket getPacket() throws Exception { verify(mDeps, times(1)).sendPacket(any(), any(), any()); - return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP); + return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP, + new byte[0] /* optionsToSkip */); } private static Inet4Address parseAddr(@Nullable String inet4Addr) { |