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