1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.dhcp; 18 19 import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; 20 import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; 21 import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; 22 import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; 23 import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_ACK; 24 import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_OFFER; 25 import static android.net.dhcp.DhcpPacket.DHCP_MTU; 26 import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; 27 import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; 28 import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; 29 import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; 30 import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; 31 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; 32 import static android.net.dhcp.DhcpPacket.ENCAP_L2; 33 import static android.net.dhcp.DhcpPacket.ENCAP_L3; 34 import static android.net.dhcp.DhcpPacket.INADDR_ANY; 35 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; 36 import static android.net.dhcp.DhcpPacket.ParseException; 37 38 import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress; 39 import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address; 40 41 import static org.junit.Assert.assertEquals; 42 import static org.junit.Assert.assertNotNull; 43 import static org.junit.Assert.assertNull; 44 import static org.junit.Assert.assertTrue; 45 import static org.junit.Assert.fail; 46 47 import android.annotation.Nullable; 48 import android.net.DhcpResults; 49 import android.net.InetAddresses; 50 import android.net.LinkAddress; 51 import android.net.metrics.DhcpErrorEvent; 52 53 import androidx.test.filters.SmallTest; 54 import androidx.test.runner.AndroidJUnit4; 55 56 import com.android.internal.util.HexDump; 57 58 import org.junit.After; 59 import org.junit.Before; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 63 import java.io.ByteArrayOutputStream; 64 import java.net.Inet4Address; 65 import java.nio.ByteBuffer; 66 import java.nio.charset.Charset; 67 import java.util.ArrayList; 68 import java.util.Arrays; 69 import java.util.Collections; 70 import java.util.Random; 71 72 @RunWith(AndroidJUnit4.class) 73 @SmallTest 74 public class DhcpPacketTest { 75 76 private static final Inet4Address SERVER_ADDR = v4Address("192.0.2.1"); 77 private static final Inet4Address CLIENT_ADDR = v4Address("192.0.2.234"); 78 private static final int PREFIX_LENGTH = 22; 79 private static final Inet4Address NETMASK = getPrefixMaskAsInet4Address(PREFIX_LENGTH); 80 private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress( 81 SERVER_ADDR, PREFIX_LENGTH); 82 private static final String HOSTNAME = "testhostname"; 83 private static final String CAPTIVE_PORTAL_API_URL = "https://example.com/capportapi"; 84 private static final short MTU = 1500; 85 // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code 86 // doesn't use == instead of equals when comparing addresses. 87 private static final Inet4Address ANY = v4Address("0.0.0.0"); 88 private static final byte[] TEST_EMPTY_OPTIONS_SKIP_LIST = new byte[0]; 89 private static final int TEST_IPV6_ONLY_WAIT_S = 1800; // 30 min 90 91 private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; 92 v4Address(String addrString)93 private static final Inet4Address v4Address(String addrString) throws IllegalArgumentException { 94 return (Inet4Address) InetAddresses.parseNumericAddress(addrString); 95 } 96 97 @Before setUp()98 public void setUp() { 99 DhcpPacket.testOverrideVendorId = "android-dhcp-???"; 100 } 101 102 @After tearDown()103 public void tearDown() { 104 DhcpPacket.testOverrideVendorId = null; 105 } 106 107 class TestDhcpPacket extends DhcpPacket { 108 private byte mType; 109 // TODO: Make this a map of option numbers to bytes instead. 110 private byte[] mDomainBytes, mVendorInfoBytes, mLeaseTimeBytes, mNetmaskBytes; 111 TestDhcpPacket(byte type, Inet4Address clientIp, Inet4Address yourIp)112 public TestDhcpPacket(byte type, Inet4Address clientIp, Inet4Address yourIp) { 113 super(0xdeadbeef, (short) 0, clientIp, yourIp, INADDR_ANY, INADDR_ANY, 114 CLIENT_MAC, true); 115 mType = type; 116 } 117 TestDhcpPacket(byte type)118 public TestDhcpPacket(byte type) { 119 this(type, INADDR_ANY, CLIENT_ADDR); 120 } 121 setDomainBytes(byte[] domainBytes)122 public TestDhcpPacket setDomainBytes(byte[] domainBytes) { 123 mDomainBytes = domainBytes; 124 return this; 125 } 126 setVendorInfoBytes(byte[] vendorInfoBytes)127 public TestDhcpPacket setVendorInfoBytes(byte[] vendorInfoBytes) { 128 mVendorInfoBytes = vendorInfoBytes; 129 return this; 130 } 131 setLeaseTimeBytes(byte[] leaseTimeBytes)132 public TestDhcpPacket setLeaseTimeBytes(byte[] leaseTimeBytes) { 133 mLeaseTimeBytes = leaseTimeBytes; 134 return this; 135 } 136 setNetmaskBytes(byte[] netmaskBytes)137 public TestDhcpPacket setNetmaskBytes(byte[] netmaskBytes) { 138 mNetmaskBytes = netmaskBytes; 139 return this; 140 } 141 buildPacket(int encap, short unusedDestUdp, short unusedSrcUdp)142 public ByteBuffer buildPacket(int encap, short unusedDestUdp, short unusedSrcUdp) { 143 ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); 144 fillInPacket(encap, CLIENT_ADDR, SERVER_ADDR, 145 DHCP_CLIENT, DHCP_SERVER, result, DHCP_BOOTREPLY, false); 146 return result; 147 } 148 finishPacket(ByteBuffer buffer)149 public void finishPacket(ByteBuffer buffer) { 150 addTlv(buffer, DHCP_MESSAGE_TYPE, mType); 151 if (mDomainBytes != null) { 152 addTlv(buffer, DHCP_DOMAIN_NAME, mDomainBytes); 153 } 154 if (mVendorInfoBytes != null) { 155 addTlv(buffer, DHCP_VENDOR_INFO, mVendorInfoBytes); 156 } 157 if (mLeaseTimeBytes != null) { 158 addTlv(buffer, DHCP_LEASE_TIME, mLeaseTimeBytes); 159 } 160 if (mNetmaskBytes != null) { 161 addTlv(buffer, DHCP_SUBNET_MASK, mNetmaskBytes); 162 } 163 addTlvEnd(buffer); 164 } 165 166 // Convenience method. build()167 public ByteBuffer build() { 168 // ENCAP_BOOTP packets don't contain ports, so just pass in 0. 169 ByteBuffer pkt = buildPacket(ENCAP_BOOTP, (short) 0, (short) 0); 170 pkt.flip(); 171 return pkt; 172 } 173 } 174 assertDomainAndVendorInfoParses( String expectedDomain, byte[] domainBytes, String expectedVendorInfo, byte[] vendorInfoBytes)175 private void assertDomainAndVendorInfoParses( 176 String expectedDomain, byte[] domainBytes, 177 String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception { 178 ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER) 179 .setDomainBytes(domainBytes) 180 .setVendorInfoBytes(vendorInfoBytes) 181 .build(); 182 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, 183 TEST_EMPTY_OPTIONS_SKIP_LIST); 184 assertEquals(expectedDomain, offerPacket.mDomainName); 185 assertEquals(expectedVendorInfo, offerPacket.mVendorInfo); 186 } 187 188 @Test testDomainName()189 public void testDomainName() throws Exception { 190 byte[] nullByte = new byte[] { 0x00 }; 191 byte[] twoNullBytes = new byte[] { 0x00, 0x00 }; 192 byte[] nonNullDomain = new byte[] { 193 (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l' 194 }; 195 byte[] trailingNullDomain = new byte[] { 196 (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l', 0x00 197 }; 198 byte[] embeddedNullsDomain = new byte[] { 199 (byte) 'g', (byte) 'o', (byte) 'o', 0x00, 0x00, (byte) 'g', (byte) 'l' 200 }; 201 byte[] metered = "ANDROID_METERED".getBytes("US-ASCII"); 202 203 byte[] meteredEmbeddedNull = metered.clone(); 204 meteredEmbeddedNull[7] = (char) 0; 205 206 byte[] meteredTrailingNull = metered.clone(); 207 meteredTrailingNull[meteredTrailingNull.length - 1] = (char) 0; 208 209 assertDomainAndVendorInfoParses("", nullByte, "\u0000", nullByte); 210 assertDomainAndVendorInfoParses("", twoNullBytes, "\u0000\u0000", twoNullBytes); 211 assertDomainAndVendorInfoParses("goo.gl", nonNullDomain, "ANDROID_METERED", metered); 212 assertDomainAndVendorInfoParses("goo", embeddedNullsDomain, 213 "ANDROID\u0000METERED", meteredEmbeddedNull); 214 assertDomainAndVendorInfoParses("goo.gl", trailingNullDomain, 215 "ANDROID_METERE\u0000", meteredTrailingNull); 216 } 217 assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime, long leaseTimeMillis, byte[] leaseTimeBytes)218 private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime, 219 long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception { 220 TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER); 221 if (leaseTimeBytes != null) { 222 testPacket.setLeaseTimeBytes(leaseTimeBytes); 223 } 224 ByteBuffer packet = testPacket.build(); 225 DhcpPacket offerPacket = null; 226 227 if (!expectValid) { 228 try { 229 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, 230 TEST_EMPTY_OPTIONS_SKIP_LIST); 231 fail("Invalid packet parsed successfully: " + offerPacket); 232 } catch (ParseException expected) { 233 } 234 return; 235 } 236 237 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, 238 TEST_EMPTY_OPTIONS_SKIP_LIST); 239 assertNotNull(offerPacket); 240 assertEquals(rawLeaseTime, offerPacket.mLeaseTime); 241 DhcpResults dhcpResults = offerPacket.toDhcpResults(); // Just check this doesn't crash. 242 assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis()); 243 } 244 245 @Test testLeaseTime()246 public void testLeaseTime() throws Exception { 247 byte[] noLease = null; 248 byte[] tooShortLease = new byte[] { 0x00, 0x00 }; 249 byte[] tooLongLease = new byte[] { 0x00, 0x00, 0x00, 60, 0x01 }; 250 byte[] zeroLease = new byte[] { 0x00, 0x00, 0x00, 0x00 }; 251 byte[] tenSecondLease = new byte[] { 0x00, 0x00, 0x00, 10 }; 252 byte[] oneMinuteLease = new byte[] { 0x00, 0x00, 0x00, 60 }; 253 byte[] fiveMinuteLease = new byte[] { 0x00, 0x00, 0x01, 0x2c }; 254 byte[] oneDayLease = new byte[] { 0x00, 0x01, 0x51, (byte) 0x80 }; 255 byte[] maxIntPlusOneLease = new byte[] { (byte) 0x80, 0x00, 0x00, 0x01 }; 256 byte[] infiniteLease = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; 257 258 assertLeaseTimeParses(true, null, 0, noLease); 259 assertLeaseTimeParses(false, null, 0, tooShortLease); 260 assertLeaseTimeParses(false, null, 0, tooLongLease); 261 assertLeaseTimeParses(true, 0, 60 * 1000, zeroLease); 262 assertLeaseTimeParses(true, 10, 60 * 1000, tenSecondLease); 263 assertLeaseTimeParses(true, 60, 60 * 1000, oneMinuteLease); 264 assertLeaseTimeParses(true, 300, 300 * 1000, fiveMinuteLease); 265 assertLeaseTimeParses(true, 86400, 86400 * 1000, oneDayLease); 266 assertLeaseTimeParses(true, -2147483647, 2147483649L * 1000, maxIntPlusOneLease); 267 assertLeaseTimeParses(true, DhcpPacket.INFINITE_LEASE, 0, infiniteLease); 268 } 269 checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp, byte[] netmaskBytes)270 private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp, 271 byte[] netmaskBytes) throws Exception { 272 checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes); 273 checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes); 274 } 275 checkIpAddress(String expected, byte type, Inet4Address clientIp, Inet4Address yourIp, byte[] netmaskBytes)276 private void checkIpAddress(String expected, byte type, 277 Inet4Address clientIp, Inet4Address yourIp, 278 byte[] netmaskBytes) throws Exception { 279 ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp) 280 .setNetmaskBytes(netmaskBytes) 281 .build(); 282 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP, 283 TEST_EMPTY_OPTIONS_SKIP_LIST); 284 DhcpResults results = offerPacket.toDhcpResults(); 285 286 if (expected != null) { 287 LinkAddress expectedAddress = new LinkAddress(expected); 288 assertEquals(expectedAddress, results.ipAddress); 289 } else { 290 assertNull(results); 291 } 292 } 293 294 @Test testIpAddress()295 public void testIpAddress() throws Exception { 296 byte[] slash11Netmask = new byte[] { (byte) 0xff, (byte) 0xe0, 0x00, 0x00 }; 297 byte[] slash24Netmask = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 }; 298 byte[] invalidNetmask = new byte[] { (byte) 0xff, (byte) 0xfb, (byte) 0xff, 0x00 }; 299 Inet4Address example1 = v4Address("192.0.2.1"); 300 Inet4Address example2 = v4Address("192.0.2.43"); 301 302 // A packet without any addresses is not valid. 303 checkIpAddress(null, ANY, ANY, slash24Netmask); 304 305 // ClientIP is used iff YourIP is not present. 306 checkIpAddress("192.0.2.1/24", example2, example1, slash24Netmask); 307 checkIpAddress("192.0.2.43/11", example2, ANY, slash11Netmask); 308 checkIpAddress("192.0.2.43/11", ANY, example2, slash11Netmask); 309 310 // Invalid netmasks are ignored. 311 checkIpAddress(null, example2, ANY, invalidNetmask); 312 313 // If there is no netmask, implicit netmasks are used. 314 checkIpAddress("192.0.2.43/24", ANY, example2, null); 315 } 316 assertDhcpResults(String ipAddress, String gateway, String dnsServersString, String domains, String serverAddress, String serverHostName, String vendorInfo, int leaseDuration, boolean hasMeteredHint, int mtu, DhcpResults dhcpResults)317 private void assertDhcpResults(String ipAddress, String gateway, String dnsServersString, 318 String domains, String serverAddress, String serverHostName, String vendorInfo, 319 int leaseDuration, boolean hasMeteredHint, int mtu, DhcpResults dhcpResults) 320 throws Exception { 321 assertEquals(new LinkAddress(ipAddress), dhcpResults.ipAddress); 322 assertEquals(v4Address(gateway), dhcpResults.gateway); 323 324 String[] dnsServerStrings = dnsServersString.split(","); 325 ArrayList dnsServers = new ArrayList(); 326 for (String dnsServerString : dnsServerStrings) { 327 dnsServers.add(v4Address(dnsServerString)); 328 } 329 assertEquals(dnsServers, dhcpResults.dnsServers); 330 331 assertEquals(domains, dhcpResults.domains); 332 assertEquals(v4Address(serverAddress), dhcpResults.serverAddress); 333 assertEquals(serverHostName, dhcpResults.serverHostName); 334 assertEquals(vendorInfo, dhcpResults.vendorInfo); 335 assertEquals(leaseDuration, dhcpResults.leaseDuration); 336 assertEquals(hasMeteredHint, dhcpResults.hasMeteredHint()); 337 assertEquals(mtu, dhcpResults.mtu); 338 } 339 340 @Test testOffer1()341 public void testOffer1() throws Exception { 342 // TODO: Turn all of these into golden files. This will probably require using 343 // androidx.test.InstrumentationRegistry for obtaining a Context object 344 // to read such golden files, along with an appropriate Android.mk. 345 // CHECKSTYLE:OFF Generated code 346 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 347 // IP header. 348 "451001480000000080118849c0a89003c0a89ff7" + 349 // UDP header. 350 "004300440134dcfa" + 351 // BOOTP header. 352 "02010600c997a63b0000000000000000c0a89ff70000000000000000" + 353 // MAC address. 354 "30766ff2a90c00000000000000000000" + 355 // Server name. 356 "0000000000000000000000000000000000000000000000000000000000000000" + 357 "0000000000000000000000000000000000000000000000000000000000000000" + 358 // File. 359 "0000000000000000000000000000000000000000000000000000000000000000" + 360 "0000000000000000000000000000000000000000000000000000000000000000" + 361 "0000000000000000000000000000000000000000000000000000000000000000" + 362 "0000000000000000000000000000000000000000000000000000000000000000" + 363 // Options 364 "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" + 365 "3a0400000e103b040000189cff00000000000000000000")); 366 // CHECKSTYLE:ON Generated code 367 368 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 369 TEST_EMPTY_OPTIONS_SKIP_LIST); 370 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 371 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 372 assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", 373 null, "192.168.144.3", "", null, 7200, false, 0, dhcpResults); 374 } 375 376 @Test testOffer2()377 public void testOffer2() throws Exception { 378 // CHECKSTYLE:OFF Generated code 379 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 380 // IP header. 381 "450001518d0600004011144dc0a82b01c0a82bf7" + 382 // UDP header. 383 "00430044013d9ac7" + 384 // BOOTP header. 385 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 386 // MAC address. 387 "30766ff2a90c00000000000000000000" + 388 // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator). 389 "646863702e616e64726f69642e636f6d00000000000000000000000000000000" + 390 "0000000000004141414100000000000000000000000000000000000000000000" + 391 // File. 392 "0000000000000000000000000000000000000000000000000000000000000000" + 393 "0000000000000000000000000000000000000000000000000000000000000000" + 394 "0000000000000000000000000000000000000000000000000000000000000000" + 395 "0000000000000000000000000000000000000000000000000000000000000000" + 396 // Options 397 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 398 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff")); 399 // CHECKSTYLE:ON Generated code 400 401 assertEquals(337, packet.limit()); 402 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 403 TEST_EMPTY_OPTIONS_SKIP_LIST); 404 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 405 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 406 assertDhcpResults("192.168.43.247/24", "192.168.43.1", "192.168.43.1", 407 null, "192.168.43.1", "dhcp.android.com", "ANDROID_METERED", 3600, true, 0, 408 dhcpResults); 409 assertTrue(dhcpResults.hasMeteredHint()); 410 } 411 runCapportOptionTest(boolean enabled)412 private void runCapportOptionTest(boolean enabled) throws Exception { 413 // CHECKSTYLE:OFF Generated code 414 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 415 // IP header. 416 "450001518d0600004011144dc0a82b01c0a82bf7" + 417 // UDP header 418 "00430044013d9ac7" + 419 // BOOTP header 420 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 421 // MAC address. 422 "30766ff2a90c00000000000000000000" + 423 // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator). 424 "646863702e616e64726f69642e636f6d00000000000000000000000000000000" + 425 "0000000000004141414100000000000000000000000000000000000000000000" + 426 // File. 427 "0000000000000000000000000000000000000000000000000000000000000000" + 428 "0000000000000000000000000000000000000000000000000000000000000000" + 429 "0000000000000000000000000000000000000000000000000000000000000000" + 430 "0000000000000000000000000000000000000000000000000000000000000000" + 431 // Options 432 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 433 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544721d" + 434 "68747470733a2f2f706f7274616c6170692e6578616d706c652e636f6dff")); 435 // CHECKSTYLE:ON Generated code 436 437 final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 438 enabled ? TEST_EMPTY_OPTIONS_SKIP_LIST 439 : new byte[] { DhcpPacket.DHCP_CAPTIVE_PORTAL }); 440 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 441 final DhcpResults dhcpResults = offerPacket.toDhcpResults(); 442 final String testUrl = enabled ? "https://portalapi.example.com" : null; 443 assertEquals(testUrl, dhcpResults.captivePortalApiUrl); 444 } 445 446 @Test testCapportOption()447 public void testCapportOption() throws Exception { 448 runCapportOptionTest(true /* enabled */); 449 } 450 451 @Test testCapportOption_Disabled()452 public void testCapportOption_Disabled() throws Exception { 453 runCapportOptionTest(false /* enabled */); 454 } 455 456 @Test testCapportOption_Invalid()457 public void testCapportOption_Invalid() throws Exception { 458 // CHECKSTYLE:OFF Generated code 459 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 460 // IP header. 461 "450001518d0600004011144dc0a82b01c0a82bf7" + 462 // UDP header 463 "00430044013d9ac7" + 464 // BOOTP header 465 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 466 // MAC address. 467 "30766ff2a90c00000000000000000000" + 468 // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator). 469 "646863702e616e64726f69642e636f6d00000000000000000000000000000000" + 470 "0000000000004141414100000000000000000000000000000000000000000000" + 471 // File. 472 "0000000000000000000000000000000000000000000000000000000000000000" + 473 "0000000000000000000000000000000000000000000000000000000000000000" + 474 "0000000000000000000000000000000000000000000000000000000000000000" + 475 "0000000000000000000000000000000000000000000000000000000000000000" + 476 // Options 477 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 478 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544" + 479 // Option 114 (0x72, capport), length 10 (0x0a) 480 "720a" + 481 // バグ-com in UTF-8, plus the ff byte that marks the end of options. 482 "e38390e382b02d636f6dff")); 483 // CHECKSTYLE:ON Generated code 484 485 final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 486 TEST_EMPTY_OPTIONS_SKIP_LIST); 487 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 488 final DhcpResults dhcpResults = offerPacket.toDhcpResults(); 489 // Output URL will be garbled because some characters do not exist in the target charset, 490 // but the parser should not crash. 491 assertTrue(dhcpResults.captivePortalApiUrl.length() > 0); 492 } 493 runIPv6OnlyPreferredOption(boolean enabled)494 private void runIPv6OnlyPreferredOption(boolean enabled) throws Exception { 495 // CHECKSTYLE:OFF Generated code 496 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 497 // IP header. 498 "45100158000040004011B5CEC0A80164C0A80102" + 499 // UDP header 500 "004300440144CE63" + 501 // BOOTP header 502 "02010600B8BF41E60000000000000000C0A80102C0A8016400000000" + 503 // MAC address. 504 "22B3614EE01200000000000000000000" + 505 // Server name and padding. 506 "0000000000000000000000000000000000000000000000000000000000000000" + 507 "0000000000000000000000000000000000000000000000000000000000000000" + 508 // File. 509 "0000000000000000000000000000000000000000000000000000000000000000" + 510 "0000000000000000000000000000000000000000000000000000000000000000" + 511 "0000000000000000000000000000000000000000000000000000000000000000" + 512 "0000000000000000000000000000000000000000000000000000000000000000" + 513 // Options 514 "638253633501023604C0A80164330400000E103A04000007083B0400000C4E01" + 515 "04FFFFFF001C04C0A801FF0304C0A801640604C0A801640C0C74657374686F73" + 516 "746E616D651A0205DC" + 517 // Option 108 (0x6c, IPv6-Only preferred option), length 4 (0x04), 1800s 518 "6C0400000708" + 519 // End of options. 520 "FF")); 521 // CHECKSTYLE:ON Generated code 522 523 final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 524 enabled ? TEST_EMPTY_OPTIONS_SKIP_LIST 525 : new byte[] { DhcpPacket.DHCP_IPV6_ONLY_PREFERRED }); 526 assertTrue(offerPacket instanceof DhcpOfferPacket); 527 assertEquals(offerPacket.mIpv6OnlyWaitTime, 528 enabled ? new Integer(TEST_IPV6_ONLY_WAIT_S) : null); 529 } 530 531 @Test testIPv6OnlyPreferredOption()532 public void testIPv6OnlyPreferredOption() throws Exception { 533 runIPv6OnlyPreferredOption(true /* enabled */); 534 } 535 536 @Test testIPv6OnlyPreferredOption_Disable()537 public void testIPv6OnlyPreferredOption_Disable() throws Exception { 538 runIPv6OnlyPreferredOption(false /* enabled */); 539 } 540 541 @Test testBadIpPacket()542 public void testBadIpPacket() throws Exception { 543 final byte[] packet = HexDump.hexStringToByteArray( 544 // IP header. 545 "450001518d0600004011144dc0a82b01c0a82bf7"); 546 547 try { 548 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3, 549 TEST_EMPTY_OPTIONS_SKIP_LIST); 550 } catch (DhcpPacket.ParseException expected) { 551 assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); 552 return; 553 } 554 fail("Dhcp packet parsing should have failed"); 555 } 556 557 @Test testBadDhcpPacket()558 public void testBadDhcpPacket() throws Exception { 559 final byte[] packet = HexDump.hexStringToByteArray( 560 // IP header. 561 "450001518d0600004011144dc0a82b01c0a82bf7" + 562 // UDP header. 563 "00430044013d9ac7" + 564 // BOOTP header. 565 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000"); 566 567 try { 568 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 569 } catch (DhcpPacket.ParseException expected) { 570 assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); 571 return; 572 } 573 fail("Dhcp packet parsing should have failed"); 574 } 575 576 @Test testBadTruncatedOffer()577 public void testBadTruncatedOffer() throws Exception { 578 final byte[] packet = HexDump.hexStringToByteArray( 579 // IP header. 580 "450001518d0600004011144dc0a82b01c0a82bf7" + 581 // UDP header. 582 "00430044013d9ac7" + 583 // BOOTP header. 584 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 585 // MAC address. 586 "30766ff2a90c00000000000000000000" + 587 // Server name. 588 "0000000000000000000000000000000000000000000000000000000000000000" + 589 "0000000000000000000000000000000000000000000000000000000000000000" + 590 // File, missing one byte 591 "0000000000000000000000000000000000000000000000000000000000000000" + 592 "0000000000000000000000000000000000000000000000000000000000000000" + 593 "0000000000000000000000000000000000000000000000000000000000000000" + 594 "00000000000000000000000000000000000000000000000000000000000000"); 595 596 try { 597 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 598 } catch (DhcpPacket.ParseException expected) { 599 assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); 600 return; 601 } 602 fail("Dhcp packet parsing should have failed"); 603 } 604 605 @Test testBadOfferWithoutACookie()606 public void testBadOfferWithoutACookie() throws Exception { 607 final byte[] packet = HexDump.hexStringToByteArray( 608 // IP header. 609 "450001518d0600004011144dc0a82b01c0a82bf7" + 610 // UDP header. 611 "00430044013d9ac7" + 612 // BOOTP header. 613 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 614 // MAC address. 615 "30766ff2a90c00000000000000000000" + 616 // Server name. 617 "0000000000000000000000000000000000000000000000000000000000000000" + 618 "0000000000000000000000000000000000000000000000000000000000000000" + 619 // File. 620 "0000000000000000000000000000000000000000000000000000000000000000" + 621 "0000000000000000000000000000000000000000000000000000000000000000" + 622 "0000000000000000000000000000000000000000000000000000000000000000" + 623 "0000000000000000000000000000000000000000000000000000000000000000" 624 // No options 625 ); 626 627 try { 628 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 629 } catch (DhcpPacket.ParseException expected) { 630 assertDhcpErrorCodes(DhcpErrorEvent.DHCP_NO_COOKIE, expected.errorCode); 631 return; 632 } 633 fail("Dhcp packet parsing should have failed"); 634 } 635 636 @Test testOfferWithBadCookie()637 public void testOfferWithBadCookie() throws Exception { 638 final byte[] packet = HexDump.hexStringToByteArray( 639 // IP header. 640 "450001518d0600004011144dc0a82b01c0a82bf7" + 641 // UDP header. 642 "00430044013d9ac7" + 643 // BOOTP header. 644 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 645 // MAC address. 646 "30766ff2a90c00000000000000000000" + 647 // Server name. 648 "0000000000000000000000000000000000000000000000000000000000000000" + 649 "0000000000000000000000000000000000000000000000000000000000000000" + 650 // File. 651 "0000000000000000000000000000000000000000000000000000000000000000" + 652 "0000000000000000000000000000000000000000000000000000000000000000" + 653 "0000000000000000000000000000000000000000000000000000000000000000" + 654 "0000000000000000000000000000000000000000000000000000000000000000" + 655 // Bad cookie 656 "DEADBEEF3501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 657 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"); 658 659 try { 660 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 661 } catch (DhcpPacket.ParseException expected) { 662 assertDhcpErrorCodes(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE, expected.errorCode); 663 return; 664 } 665 fail("Dhcp packet parsing should have failed"); 666 } 667 assertDhcpErrorCodes(int expected, int got)668 private void assertDhcpErrorCodes(int expected, int got) { 669 assertEquals(Integer.toHexString(expected), Integer.toHexString(got)); 670 } 671 672 @Test testTruncatedOfferPackets()673 public void testTruncatedOfferPackets() throws Exception { 674 final byte[] packet = HexDump.hexStringToByteArray( 675 // IP header. 676 "450001518d0600004011144dc0a82b01c0a82bf7" + 677 // UDP header. 678 "00430044013d9ac7" + 679 // BOOTP header. 680 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 681 // MAC address. 682 "30766ff2a90c00000000000000000000" + 683 // Server name. 684 "0000000000000000000000000000000000000000000000000000000000000000" + 685 "0000000000000000000000000000000000000000000000000000000000000000" + 686 // File. 687 "0000000000000000000000000000000000000000000000000000000000000000" + 688 "0000000000000000000000000000000000000000000000000000000000000000" + 689 "0000000000000000000000000000000000000000000000000000000000000000" + 690 "0000000000000000000000000000000000000000000000000000000000000000" + 691 // Options 692 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 693 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"); 694 695 for (int len = 0; len < packet.length; len++) { 696 try { 697 DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3); 698 } catch (ParseException e) { 699 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) { 700 fail(String.format("bad truncated packet of length %d", len)); 701 } 702 } 703 } 704 } 705 706 @Test testRandomPackets()707 public void testRandomPackets() throws Exception { 708 final int maxRandomPacketSize = 512; 709 final Random r = new Random(); 710 for (int i = 0; i < 10000; i++) { 711 byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; 712 r.nextBytes(packet); 713 try { 714 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 715 } catch (ParseException e) { 716 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) { 717 fail("bad packet: " + HexDump.toHexString(packet)); 718 } 719 } 720 } 721 } 722 mtuBytes(int mtu)723 private byte[] mtuBytes(int mtu) { 724 // 0x1a02: option 26, length 2. 0xff: no more options. 725 if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) { 726 throw new IllegalArgumentException( 727 String.format("Invalid MTU %d, must be 16-bit unsigned", mtu)); 728 } 729 String hexString = String.format("1a02%04xff", mtu); 730 return HexDump.hexStringToByteArray(hexString); 731 } 732 checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes)733 private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception { 734 if (mtuBytes != null) { 735 packet.position(packet.capacity() - mtuBytes.length); 736 packet.put(mtuBytes); 737 packet.clear(); 738 } 739 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 740 TEST_EMPTY_OPTIONS_SKIP_LIST); 741 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 742 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 743 assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", 744 null, "192.168.144.3", "", null, 7200, false, expectedMtu, dhcpResults); 745 } 746 747 @Test testMtu()748 public void testMtu() throws Exception { 749 // CHECKSTYLE:OFF Generated code 750 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 751 // IP header. 752 "451001480000000080118849c0a89003c0a89ff7" + 753 // UDP header. 754 "004300440134dcfa" + 755 // BOOTP header. 756 "02010600c997a63b0000000000000000c0a89ff70000000000000000" + 757 // MAC address. 758 "30766ff2a90c00000000000000000000" + 759 // Server name. 760 "0000000000000000000000000000000000000000000000000000000000000000" + 761 "0000000000000000000000000000000000000000000000000000000000000000" + 762 // File. 763 "0000000000000000000000000000000000000000000000000000000000000000" + 764 "0000000000000000000000000000000000000000000000000000000000000000" + 765 "0000000000000000000000000000000000000000000000000000000000000000" + 766 "0000000000000000000000000000000000000000000000000000000000000000" + 767 // Options 768 "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" + 769 "3a0400000e103b040000189cff00000000")); 770 // CHECKSTYLE:ON Generated code 771 772 checkMtu(packet, 0, null); 773 checkMtu(packet, 0, mtuBytes(1501)); 774 checkMtu(packet, 1500, mtuBytes(1500)); 775 checkMtu(packet, 1499, mtuBytes(1499)); 776 checkMtu(packet, 1280, mtuBytes(1280)); 777 checkMtu(packet, 0, mtuBytes(1279)); 778 checkMtu(packet, 0, mtuBytes(576)); 779 checkMtu(packet, 0, mtuBytes(68)); 780 checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE)); 781 checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3)); 782 checkMtu(packet, 0, mtuBytes(-1)); 783 } 784 785 @Test testExplicitClientId()786 public void testExplicitClientId() throws Exception { 787 final byte[] clientId = new byte[] { 788 0x01 /* CLIENT_ID_ETH */, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; 789 790 // CHECKSTYLE:OFF Generated code 791 final byte[] packet = HexDump.hexStringToByteArray( 792 // IP header. 793 "450001518d0600004011144dc0a82b01c0a82bf7" + 794 // UDP header 795 "00430044013d9ac7" + 796 // BOOTP header 797 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 798 // MAC address. 799 "30766ff2a90c00000000000000000000" + 800 // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator). 801 "646863702e616e64726f69642e636f6d00000000000000000000000000000000" + 802 "0000000000004141414100000000000000000000000000000000000000000000" + 803 // File. 804 "0000000000000000000000000000000000000000000000000000000000000000" + 805 "0000000000000000000000000000000000000000000000000000000000000000" + 806 "0000000000000000000000000000000000000000000000000000000000000000" + 807 "0000000000000000000000000000000000000000000000000000000000000000" + 808 // Options 809 "638253633501013d0701010203040506390205dc3c0e616e64726f69642d6468" + 810 "63702d52370a0103060f1a1c333a3b2bff00"); 811 // CHECKSTYLE:ON Generated code 812 813 final DhcpPacket discoverPacket = DhcpPacket.decodeFullPacket(packet, 814 packet.length, ENCAP_L3); 815 assertTrue(discoverPacket instanceof DhcpDiscoverPacket); 816 assertTrue(discoverPacket.hasExplicitClientId()); 817 assertTrue(Arrays.equals(discoverPacket.mClientId, clientId)); 818 } 819 820 @Test testBadHwaddrLength()821 public void testBadHwaddrLength() throws Exception { 822 // CHECKSTYLE:OFF Generated code 823 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 824 // IP header. 825 "450001518d0600004011144dc0a82b01c0a82bf7" + 826 // UDP header. 827 "00430044013d9ac7" + 828 // BOOTP header. 829 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 830 // MAC address. 831 "30766ff2a90c00000000000000000000" + 832 // Server name. 833 "0000000000000000000000000000000000000000000000000000000000000000" + 834 "0000000000000000000000000000000000000000000000000000000000000000" + 835 // File. 836 "0000000000000000000000000000000000000000000000000000000000000000" + 837 "0000000000000000000000000000000000000000000000000000000000000000" + 838 "0000000000000000000000000000000000000000000000000000000000000000" + 839 "0000000000000000000000000000000000000000000000000000000000000000" + 840 // Options 841 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 842 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff")); 843 // CHECKSTYLE:ON Generated code 844 String expectedClientMac = "30766FF2A90C"; 845 846 final int hwAddrLenOffset = 20 + 8 + 2; 847 assertEquals(6, packet.get(hwAddrLenOffset)); 848 849 // Expect the expected. 850 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 851 TEST_EMPTY_OPTIONS_SKIP_LIST); 852 assertNotNull(offerPacket); 853 assertEquals(6, offerPacket.getClientMac().length); 854 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 855 856 // Reduce the hardware address length and verify that it shortens the client MAC. 857 packet.flip(); 858 packet.put(hwAddrLenOffset, (byte) 5); 859 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); 860 assertNotNull(offerPacket); 861 assertEquals(5, offerPacket.getClientMac().length); 862 assertEquals(expectedClientMac.substring(0, 10), 863 HexDump.toHexString(offerPacket.getClientMac())); 864 865 packet.flip(); 866 packet.put(hwAddrLenOffset, (byte) 3); 867 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); 868 assertNotNull(offerPacket); 869 assertEquals(3, offerPacket.getClientMac().length); 870 assertEquals(expectedClientMac.substring(0, 6), 871 HexDump.toHexString(offerPacket.getClientMac())); 872 873 // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1 874 // and crash, and b) hardcode it to 6. 875 packet.flip(); 876 packet.put(hwAddrLenOffset, (byte) -1); 877 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); 878 assertNotNull(offerPacket); 879 assertEquals(6, offerPacket.getClientMac().length); 880 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 881 882 // Set the the hardware address length to a positive invalid value (> 16) and verify that we 883 // hardcode it to 6. 884 packet.flip(); 885 packet.put(hwAddrLenOffset, (byte) 17); 886 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, TEST_EMPTY_OPTIONS_SKIP_LIST); 887 assertNotNull(offerPacket); 888 assertEquals(6, offerPacket.getClientMac().length); 889 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 890 } 891 892 @Test testPadAndOverloadedOptionsOffer()893 public void testPadAndOverloadedOptionsOffer() throws Exception { 894 // A packet observed in the real world that is interesting for two reasons: 895 // 896 // 1. It uses pad bytes, which we previously didn't support correctly. 897 // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't 898 // store any information in the overloaded fields). 899 // 900 // For now, we just check that it parses correctly. 901 // CHECKSTYLE:OFF Generated code 902 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 903 // Ethernet header. 904 "b4cef6000000e80462236e300800" + 905 // IP header. 906 "4500014c00000000ff11741701010101ac119876" + 907 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 908 "004300440138ae5a" + 909 // BOOTP header. 910 "020106000fa0059f0000000000000000ac1198760000000000000000" + 911 // MAC address. 912 "b4cef600000000000000000000000000" + 913 // Server name. 914 "ff00000000000000000000000000000000000000000000000000000000000000" + 915 "0000000000000000000000000000000000000000000000000000000000000000" + 916 // File. 917 "ff00000000000000000000000000000000000000000000000000000000000000" + 918 "0000000000000000000000000000000000000000000000000000000000000000" + 919 "0000000000000000000000000000000000000000000000000000000000000000" + 920 "0000000000000000000000000000000000000000000000000000000000000000" + 921 // Options 922 "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" + 923 "0000000000000000000000000000000000000000000000ff000000")); 924 // CHECKSTYLE:ON Generated code 925 926 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, 927 TEST_EMPTY_OPTIONS_SKIP_LIST); 928 assertTrue(offerPacket instanceof DhcpOfferPacket); 929 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 930 assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1", 931 null, "1.1.1.1", "", null, 43200, false, 0, dhcpResults); 932 } 933 934 @Test testBug2111()935 public void testBug2111() throws Exception { 936 // CHECKSTYLE:OFF Generated code 937 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 938 // IP header. 939 "4500014c00000000ff119beac3eaf3880a3f5d04" + 940 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 941 "0043004401387464" + 942 // BOOTP header. 943 "0201060002554812000a0000000000000a3f5d040000000000000000" + 944 // MAC address. 945 "00904c00000000000000000000000000" + 946 // Server name. 947 "0000000000000000000000000000000000000000000000000000000000000000" + 948 "0000000000000000000000000000000000000000000000000000000000000000" + 949 // File. 950 "0000000000000000000000000000000000000000000000000000000000000000" + 951 "0000000000000000000000000000000000000000000000000000000000000000" + 952 "0000000000000000000000000000000000000000000000000000000000000000" + 953 "0000000000000000000000000000000000000000000000000000000000000000" + 954 // Options. 955 "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" + 956 "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000")); 957 // CHECKSTYLE:ON Generated code 958 959 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3, 960 TEST_EMPTY_OPTIONS_SKIP_LIST); 961 assertTrue(offerPacket instanceof DhcpOfferPacket); 962 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 963 assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2", 964 "domain123.co.uk", "192.0.2.254", "", null, 49094, false, 0, dhcpResults); 965 } 966 967 @Test testBug2136()968 public void testBug2136() throws Exception { 969 // CHECKSTYLE:OFF Generated code 970 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 971 // Ethernet header. 972 "bcf5ac000000d0c7890000000800" + 973 // IP header. 974 "4500014c00000000ff119beac3eaf3880a3f5d04" + 975 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 976 "0043004401387574" + 977 // BOOTP header. 978 "0201060163339a3000050000000000000a209ecd0000000000000000" + 979 // MAC address. 980 "bcf5ac00000000000000000000000000" + 981 // Server name. 982 "0000000000000000000000000000000000000000000000000000000000000000" + 983 "0000000000000000000000000000000000000000000000000000000000000000" + 984 // File. 985 "0000000000000000000000000000000000000000000000000000000000000000" + 986 "0000000000000000000000000000000000000000000000000000000000000000" + 987 "0000000000000000000000000000000000000000000000000000000000000000" + 988 "0000000000000000000000000000000000000000000000000000000000000000" + 989 // Options. 990 "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" + 991 "0f0b6c616e63732e61632e756b000000000000000000ff00000000")); 992 // CHECKSTYLE:ON Generated code 993 994 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, 995 TEST_EMPTY_OPTIONS_SKIP_LIST); 996 assertTrue(offerPacket instanceof DhcpOfferPacket); 997 assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac())); 998 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 999 assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53", 1000 "lancs.ac.uk", "10.32.255.128", "", null, 7200, false, 0, dhcpResults); 1001 } 1002 1003 @Test testUdpServerAnySourcePort()1004 public void testUdpServerAnySourcePort() throws Exception { 1005 // CHECKSTYLE:OFF Generated code 1006 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 1007 // Ethernet header. 1008 "9cd917000000001c2e0000000800" + 1009 // IP header. 1010 "45a00148000040003d115087d18194fb0a0f7af2" + 1011 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 1012 // NOTE: The server source port is not the canonical port 67. 1013 "C29F004401341268" + 1014 // BOOTP header. 1015 "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + 1016 // MAC address. 1017 "9cd91700000000000000000000000000" + 1018 // Server name. 1019 "0000000000000000000000000000000000000000000000000000000000000000" + 1020 "0000000000000000000000000000000000000000000000000000000000000000" + 1021 // File. 1022 "0000000000000000000000000000000000000000000000000000000000000000" + 1023 "0000000000000000000000000000000000000000000000000000000000000000" + 1024 "0000000000000000000000000000000000000000000000000000000000000000" + 1025 "0000000000000000000000000000000000000000000000000000000000000000" + 1026 // Options. 1027 "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + 1028 "d18180060f0777766d2e6564751c040a0fffffff000000")); 1029 // CHECKSTYLE:ON Generated code 1030 1031 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, 1032 TEST_EMPTY_OPTIONS_SKIP_LIST); 1033 assertTrue(offerPacket instanceof DhcpOfferPacket); 1034 assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac())); 1035 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 1036 assertDhcpResults("10.15.122.242/16", "10.15.200.23", 1037 "209.129.128.3,209.129.148.3,209.129.128.6", 1038 "wvm.edu", "10.1.105.252", "", null, 86400, false, 0, dhcpResults); 1039 } 1040 1041 @Test testUdpInvalidDstPort()1042 public void testUdpInvalidDstPort() throws Exception { 1043 // CHECKSTYLE:OFF Generated code 1044 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 1045 // Ethernet header. 1046 "9cd917000000001c2e0000000800" + 1047 // IP header. 1048 "45a00148000040003d115087d18194fb0a0f7af2" + 1049 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 1050 // NOTE: The destination port is a non-DHCP port. 1051 "0043aaaa01341268" + 1052 // BOOTP header. 1053 "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + 1054 // MAC address. 1055 "9cd91700000000000000000000000000" + 1056 // Server name. 1057 "0000000000000000000000000000000000000000000000000000000000000000" + 1058 "0000000000000000000000000000000000000000000000000000000000000000" + 1059 // File. 1060 "0000000000000000000000000000000000000000000000000000000000000000" + 1061 "0000000000000000000000000000000000000000000000000000000000000000" + 1062 "0000000000000000000000000000000000000000000000000000000000000000" + 1063 "0000000000000000000000000000000000000000000000000000000000000000" + 1064 // Options. 1065 "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + 1066 "d18180060f0777766d2e6564751c040a0fffffff000000")); 1067 // CHECKSTYLE:ON Generated code 1068 1069 try { 1070 DhcpPacket.decodeFullPacket(packet, ENCAP_L2, TEST_EMPTY_OPTIONS_SKIP_LIST); 1071 fail("Packet with invalid dst port did not throw ParseException"); 1072 } catch (ParseException expected) {} 1073 } 1074 1075 @Test testMultipleRouters()1076 public void testMultipleRouters() throws Exception { 1077 // CHECKSTYLE:OFF Generated code 1078 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 1079 // Ethernet header. 1080 "fc3d93000000" + "081735000000" + "0800" + 1081 // IP header. 1082 "45000148c2370000ff117ac2c0a8bd02ffffffff" + 1083 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 1084 "0043004401343beb" + 1085 // BOOTP header. 1086 "0201060027f518e20000800000000000c0a8bd310000000000000000" + 1087 // MAC address. 1088 "fc3d9300000000000000000000000000" + 1089 // Server name. 1090 "0000000000000000000000000000000000000000000000000000000000000000" + 1091 "0000000000000000000000000000000000000000000000000000000000000000" + 1092 // File. 1093 "0000000000000000000000000000000000000000000000000000000000000000" + 1094 "0000000000000000000000000000000000000000000000000000000000000000" + 1095 "0000000000000000000000000000000000000000000000000000000000000000" + 1096 "0000000000000000000000000000000000000000000000000000000000000000" + 1097 // Options. 1098 "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" + 1099 "0308c0a8bd01ffffff0006080808080808080404ff000000000000")); 1100 // CHECKSTYLE:ON Generated code 1101 1102 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2, 1103 TEST_EMPTY_OPTIONS_SKIP_LIST); 1104 assertTrue(offerPacket instanceof DhcpOfferPacket); 1105 assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac())); 1106 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 1107 assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4", 1108 null, "192.171.189.2", "", null, 28800, false, 0, dhcpResults); 1109 } 1110 1111 @Test testDiscoverPacket()1112 public void testDiscoverPacket() throws Exception { 1113 final short secs = 7; 1114 final int transactionId = 0xdeadbeef; 1115 final byte[] hwaddr = { 1116 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a 1117 }; 1118 final String testHostname = "android-01234567890abcde"; 1119 1120 ByteBuffer packet = DhcpPacket.buildDiscoverPacket( 1121 DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr, 1122 false /* do unicast */, DhcpClient.DEFAULT_REQUESTED_PARAMS, 1123 false /* rapid commit */, testHostname, null /* customized DHCP options */); 1124 1125 final byte[] headers = new byte[] { 1126 // Ethernet header. 1127 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 1128 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, 1129 (byte) 0x08, (byte) 0x00, 1130 // IP header. 1131 (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56, 1132 (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00, 1133 (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88, 1134 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1135 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 1136 // UDP header. 1137 (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43, 1138 (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a, 1139 // BOOTP. 1140 (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00, 1141 (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, 1142 (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00, 1143 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1144 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1145 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1146 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1147 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, 1148 (byte) 0xb1, (byte) 0x7a 1149 }; 1150 final byte[] options = new byte[] { 1151 // Magic cookie 0x63825363. 1152 (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63, 1153 // Message type DISCOVER. 1154 (byte) 0x35, (byte) 0x01, (byte) 0x01, 1155 // Client identifier Ethernet, da:01:19:5b:b1:7a. 1156 (byte) 0x3d, (byte) 0x07, 1157 (byte) 0x01, 1158 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, 1159 // Max message size 1500. 1160 (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc, 1161 // Version "android-dhcp-???". 1162 (byte) 0x3c, (byte) 0x10, 1163 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?', 1164 // Hostname "android-01234567890abcde" 1165 (byte) 0x0c, (byte) 0x18, 1166 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 1167 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 1168 // Requested parameter list. 1169 (byte) 0x37, (byte) 0x0a, 1170 DHCP_SUBNET_MASK, 1171 DHCP_ROUTER, 1172 DHCP_DNS_SERVER, 1173 DHCP_DOMAIN_NAME, 1174 DHCP_MTU, 1175 DHCP_BROADCAST_ADDRESS, 1176 DHCP_LEASE_TIME, 1177 DHCP_RENEWAL_TIME, 1178 DHCP_REBINDING_TIME, 1179 DHCP_VENDOR_INFO, 1180 // End options. 1181 (byte) 0xff, 1182 // Our packets are always of even length. TODO: find out why and possibly fix it. 1183 (byte) 0x00 1184 }; 1185 final byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length]; 1186 assertTrue((expected.length & 1) == 0); 1187 assertEquals(DhcpPacket.MIN_PACKET_LENGTH_L2, 1188 headers.length + 10 /* client hw addr padding */ + 64 /* sname */ + 128 /* file */); 1189 System.arraycopy(headers, 0, expected, 0, headers.length); 1190 System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length); 1191 1192 final byte[] actual = new byte[packet.limit()]; 1193 packet.get(actual); 1194 String msg = "Expected:\n " + Arrays.toString(expected) + "\nActual:\n " 1195 + Arrays.toString(actual); 1196 assertTrue(msg, Arrays.equals(expected, actual)); 1197 } 1198 checkBuildOfferPacket(int leaseTimeSecs, @Nullable String hostname)1199 public void checkBuildOfferPacket(int leaseTimeSecs, @Nullable String hostname) 1200 throws Exception { 1201 final int renewalTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) / 2); 1202 final int rebindingTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) * 875 / 1000); 1203 final int transactionId = 0xdeadbeef; 1204 1205 final ByteBuffer packet = DhcpPacket.buildOfferPacket( 1206 DhcpPacket.ENCAP_BOOTP, transactionId, false /* broadcast */, 1207 SERVER_ADDR, INADDR_ANY /* relayIp */, CLIENT_ADDR /* yourIp */, 1208 CLIENT_MAC, leaseTimeSecs, NETMASK /* netMask */, 1209 BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */, 1210 Collections.singletonList(SERVER_ADDR) /* dnsServers */, 1211 SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, hostname, 1212 false /* metered */, MTU, CAPTIVE_PORTAL_API_URL); 1213 1214 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 1215 // BOOTP headers 1216 bos.write(new byte[] { 1217 (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, 1218 (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, 1219 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1220 // ciaddr 1221 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 1222 }); 1223 // yiaddr 1224 bos.write(CLIENT_ADDR.getAddress()); 1225 // siaddr 1226 bos.write(SERVER_ADDR.getAddress()); 1227 // giaddr 1228 bos.write(INADDR_ANY.getAddress()); 1229 // chaddr 1230 bos.write(CLIENT_MAC); 1231 1232 // Padding 1233 bos.write(new byte[202]); 1234 1235 // Options 1236 bos.write(new byte[]{ 1237 // Magic cookie 0x63825363. 1238 (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63, 1239 // Message type OFFER. 1240 (byte) 0x35, (byte) 0x01, (byte) 0x02, 1241 }); 1242 // Server ID 1243 bos.write(new byte[] { (byte) 0x36, (byte) 0x04 }); 1244 bos.write(SERVER_ADDR.getAddress()); 1245 // Lease time 1246 bos.write(new byte[] { (byte) 0x33, (byte) 0x04 }); 1247 bos.write(intToByteArray(leaseTimeSecs)); 1248 if (leaseTimeSecs != INFINITE_LEASE) { 1249 // Renewal time 1250 bos.write(new byte[]{(byte) 0x3a, (byte) 0x04}); 1251 bos.write(intToByteArray(renewalTime)); 1252 // Rebinding time 1253 bos.write(new byte[]{(byte) 0x3b, (byte) 0x04}); 1254 bos.write(intToByteArray(rebindingTime)); 1255 } 1256 // Subnet mask 1257 bos.write(new byte[] { (byte) 0x01, (byte) 0x04 }); 1258 bos.write(NETMASK.getAddress()); 1259 // Broadcast address 1260 bos.write(new byte[] { (byte) 0x1c, (byte) 0x04 }); 1261 bos.write(BROADCAST_ADDR.getAddress()); 1262 // Router 1263 bos.write(new byte[] { (byte) 0x03, (byte) 0x04 }); 1264 bos.write(SERVER_ADDR.getAddress()); 1265 // Nameserver 1266 bos.write(new byte[] { (byte) 0x06, (byte) 0x04 }); 1267 bos.write(SERVER_ADDR.getAddress()); 1268 // Hostname 1269 if (hostname != null) { 1270 bos.write(new byte[]{(byte) 0x0c, (byte) hostname.length()}); 1271 bos.write(hostname.getBytes(Charset.forName("US-ASCII"))); 1272 } 1273 // MTU 1274 bos.write(new byte[] { (byte) 0x1a, (byte) 0x02 }); 1275 bos.write(shortToByteArray(MTU)); 1276 // capport URL. Option 114 = 0x72 1277 bos.write(new byte[] { (byte) 0x72, (byte) CAPTIVE_PORTAL_API_URL.length() }); 1278 bos.write(CAPTIVE_PORTAL_API_URL.getBytes(Charset.forName("US-ASCII"))); 1279 // End options. 1280 bos.write(0xff); 1281 1282 if ((bos.size() & 1) != 0) { 1283 bos.write(0x00); 1284 } 1285 1286 final byte[] expected = bos.toByteArray(); 1287 final byte[] actual = new byte[packet.limit()]; 1288 packet.get(actual); 1289 final String msg = "Expected:\n " + HexDump.dumpHexString(expected) + 1290 "\nActual:\n " + HexDump.dumpHexString(actual); 1291 assertTrue(msg, Arrays.equals(expected, actual)); 1292 } 1293 1294 @Test testOfferPacket()1295 public void testOfferPacket() throws Exception { 1296 checkBuildOfferPacket(3600, HOSTNAME); 1297 checkBuildOfferPacket(Integer.MAX_VALUE, HOSTNAME); 1298 checkBuildOfferPacket(0x80000000, HOSTNAME); 1299 checkBuildOfferPacket(INFINITE_LEASE, HOSTNAME); 1300 checkBuildOfferPacket(3600, null); 1301 } 1302 intToByteArray(int val)1303 private static byte[] intToByteArray(int val) { 1304 return ByteBuffer.allocate(4).putInt(val).array(); 1305 } 1306 shortToByteArray(short val)1307 private static byte[] shortToByteArray(short val) { 1308 return ByteBuffer.allocate(2).putShort(val).array(); 1309 } 1310 } 1311