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 17package android.net.dhcp; 18 19import static android.net.dhcp.DhcpPacket.*; 20import static org.junit.Assert.assertEquals; 21import static org.junit.Assert.assertNotNull; 22import static org.junit.Assert.assertNull; 23import static org.junit.Assert.assertTrue; 24import static org.junit.Assert.fail; 25 26import android.net.DhcpResults; 27import android.net.LinkAddress; 28import android.net.NetworkUtils; 29import android.net.metrics.DhcpErrorEvent; 30import android.support.test.runner.AndroidJUnit4; 31import android.support.test.filters.SmallTest; 32import android.system.OsConstants; 33 34import com.android.internal.util.HexDump; 35 36import java.net.Inet4Address; 37import java.nio.ByteBuffer; 38import java.util.ArrayList; 39import java.util.Arrays; 40import java.util.Random; 41 42import org.junit.Before; 43import org.junit.Test; 44import org.junit.runner.RunWith; 45 46@RunWith(AndroidJUnit4.class) 47@SmallTest 48public class DhcpPacketTest { 49 50 private static Inet4Address SERVER_ADDR = v4Address("192.0.2.1"); 51 private static Inet4Address CLIENT_ADDR = v4Address("192.0.2.234"); 52 // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code 53 // doesn't use == instead of equals when comparing addresses. 54 private static Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0"); 55 56 private static byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; 57 58 private static final Inet4Address v4Address(String addrString) throws IllegalArgumentException { 59 return (Inet4Address) NetworkUtils.numericToInetAddress(addrString); 60 } 61 62 @Before 63 public void setUp() { 64 DhcpPacket.testOverrideVendorId = "android-dhcp-???"; 65 DhcpPacket.testOverrideHostname = "android-01234567890abcde"; 66 } 67 68 class TestDhcpPacket extends DhcpPacket { 69 private byte mType; 70 // TODO: Make this a map of option numbers to bytes instead. 71 private byte[] mDomainBytes, mVendorInfoBytes, mLeaseTimeBytes, mNetmaskBytes; 72 73 public TestDhcpPacket(byte type, Inet4Address clientIp, Inet4Address yourIp) { 74 super(0xdeadbeef, (short) 0, clientIp, yourIp, INADDR_ANY, INADDR_ANY, 75 CLIENT_MAC, true); 76 mType = type; 77 } 78 79 public TestDhcpPacket(byte type) { 80 this(type, INADDR_ANY, CLIENT_ADDR); 81 } 82 83 public TestDhcpPacket setDomainBytes(byte[] domainBytes) { 84 mDomainBytes = domainBytes; 85 return this; 86 } 87 88 public TestDhcpPacket setVendorInfoBytes(byte[] vendorInfoBytes) { 89 mVendorInfoBytes = vendorInfoBytes; 90 return this; 91 } 92 93 public TestDhcpPacket setLeaseTimeBytes(byte[] leaseTimeBytes) { 94 mLeaseTimeBytes = leaseTimeBytes; 95 return this; 96 } 97 98 public TestDhcpPacket setNetmaskBytes(byte[] netmaskBytes) { 99 mNetmaskBytes = netmaskBytes; 100 return this; 101 } 102 103 public ByteBuffer buildPacket(int encap, short unusedDestUdp, short unusedSrcUdp) { 104 ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); 105 fillInPacket(encap, CLIENT_ADDR, SERVER_ADDR, 106 DHCP_CLIENT, DHCP_SERVER, result, DHCP_BOOTREPLY, false); 107 return result; 108 } 109 110 public void finishPacket(ByteBuffer buffer) { 111 addTlv(buffer, DHCP_MESSAGE_TYPE, mType); 112 if (mDomainBytes != null) { 113 addTlv(buffer, DHCP_DOMAIN_NAME, mDomainBytes); 114 } 115 if (mVendorInfoBytes != null) { 116 addTlv(buffer, DHCP_VENDOR_INFO, mVendorInfoBytes); 117 } 118 if (mLeaseTimeBytes != null) { 119 addTlv(buffer, DHCP_LEASE_TIME, mLeaseTimeBytes); 120 } 121 if (mNetmaskBytes != null) { 122 addTlv(buffer, DHCP_SUBNET_MASK, mNetmaskBytes); 123 } 124 addTlvEnd(buffer); 125 } 126 127 // Convenience method. 128 public ByteBuffer build() { 129 // ENCAP_BOOTP packets don't contain ports, so just pass in 0. 130 ByteBuffer pkt = buildPacket(ENCAP_BOOTP, (short) 0, (short) 0); 131 pkt.flip(); 132 return pkt; 133 } 134 } 135 136 private void assertDomainAndVendorInfoParses( 137 String expectedDomain, byte[] domainBytes, 138 String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception { 139 ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER) 140 .setDomainBytes(domainBytes) 141 .setVendorInfoBytes(vendorInfoBytes) 142 .build(); 143 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); 144 assertEquals(expectedDomain, offerPacket.mDomainName); 145 assertEquals(expectedVendorInfo, offerPacket.mVendorInfo); 146 } 147 148 @Test 149 public void testDomainName() throws Exception { 150 byte[] nullByte = new byte[] { 0x00 }; 151 byte[] twoNullBytes = new byte[] { 0x00, 0x00 }; 152 byte[] nonNullDomain = new byte[] { 153 (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l' 154 }; 155 byte[] trailingNullDomain = new byte[] { 156 (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l', 0x00 157 }; 158 byte[] embeddedNullsDomain = new byte[] { 159 (byte) 'g', (byte) 'o', (byte) 'o', 0x00, 0x00, (byte) 'g', (byte) 'l' 160 }; 161 byte[] metered = "ANDROID_METERED".getBytes("US-ASCII"); 162 163 byte[] meteredEmbeddedNull = metered.clone(); 164 meteredEmbeddedNull[7] = (char) 0; 165 166 byte[] meteredTrailingNull = metered.clone(); 167 meteredTrailingNull[meteredTrailingNull.length - 1] = (char) 0; 168 169 assertDomainAndVendorInfoParses("", nullByte, "\u0000", nullByte); 170 assertDomainAndVendorInfoParses("", twoNullBytes, "\u0000\u0000", twoNullBytes); 171 assertDomainAndVendorInfoParses("goo.gl", nonNullDomain, "ANDROID_METERED", metered); 172 assertDomainAndVendorInfoParses("goo", embeddedNullsDomain, 173 "ANDROID\u0000METERED", meteredEmbeddedNull); 174 assertDomainAndVendorInfoParses("goo.gl", trailingNullDomain, 175 "ANDROID_METERE\u0000", meteredTrailingNull); 176 } 177 178 private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime, 179 long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception { 180 TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER); 181 if (leaseTimeBytes != null) { 182 testPacket.setLeaseTimeBytes(leaseTimeBytes); 183 } 184 ByteBuffer packet = testPacket.build(); 185 DhcpPacket offerPacket = null; 186 187 if (!expectValid) { 188 try { 189 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); 190 fail("Invalid packet parsed successfully: " + offerPacket); 191 } catch (ParseException expected) { 192 } 193 return; 194 } 195 196 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); 197 assertNotNull(offerPacket); 198 assertEquals(rawLeaseTime, offerPacket.mLeaseTime); 199 DhcpResults dhcpResults = offerPacket.toDhcpResults(); // Just check this doesn't crash. 200 assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis()); 201 } 202 203 @Test 204 public void testLeaseTime() throws Exception { 205 byte[] noLease = null; 206 byte[] tooShortLease = new byte[] { 0x00, 0x00 }; 207 byte[] tooLongLease = new byte[] { 0x00, 0x00, 0x00, 60, 0x01 }; 208 byte[] zeroLease = new byte[] { 0x00, 0x00, 0x00, 0x00 }; 209 byte[] tenSecondLease = new byte[] { 0x00, 0x00, 0x00, 10 }; 210 byte[] oneMinuteLease = new byte[] { 0x00, 0x00, 0x00, 60 }; 211 byte[] fiveMinuteLease = new byte[] { 0x00, 0x00, 0x01, 0x2c }; 212 byte[] oneDayLease = new byte[] { 0x00, 0x01, 0x51, (byte) 0x80 }; 213 byte[] maxIntPlusOneLease = new byte[] { (byte) 0x80, 0x00, 0x00, 0x01 }; 214 byte[] infiniteLease = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; 215 216 assertLeaseTimeParses(true, null, 0, noLease); 217 assertLeaseTimeParses(false, null, 0, tooShortLease); 218 assertLeaseTimeParses(false, null, 0, tooLongLease); 219 assertLeaseTimeParses(true, 0, 60 * 1000, zeroLease); 220 assertLeaseTimeParses(true, 10, 60 * 1000, tenSecondLease); 221 assertLeaseTimeParses(true, 60, 60 * 1000, oneMinuteLease); 222 assertLeaseTimeParses(true, 300, 300 * 1000, fiveMinuteLease); 223 assertLeaseTimeParses(true, 86400, 86400 * 1000, oneDayLease); 224 assertLeaseTimeParses(true, -2147483647, 2147483649L * 1000, maxIntPlusOneLease); 225 assertLeaseTimeParses(true, DhcpPacket.INFINITE_LEASE, 0, infiniteLease); 226 } 227 228 private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp, 229 byte[] netmaskBytes) throws Exception { 230 checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes); 231 checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes); 232 } 233 234 private void checkIpAddress(String expected, byte type, 235 Inet4Address clientIp, Inet4Address yourIp, 236 byte[] netmaskBytes) throws Exception { 237 ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp) 238 .setNetmaskBytes(netmaskBytes) 239 .build(); 240 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); 241 DhcpResults results = offerPacket.toDhcpResults(); 242 243 if (expected != null) { 244 LinkAddress expectedAddress = new LinkAddress(expected); 245 assertEquals(expectedAddress, results.ipAddress); 246 } else { 247 assertNull(results); 248 } 249 } 250 251 @Test 252 public void testIpAddress() throws Exception { 253 byte[] slash11Netmask = new byte[] { (byte) 0xff, (byte) 0xe0, 0x00, 0x00 }; 254 byte[] slash24Netmask = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 }; 255 byte[] invalidNetmask = new byte[] { (byte) 0xff, (byte) 0xfb, (byte) 0xff, 0x00 }; 256 Inet4Address example1 = v4Address("192.0.2.1"); 257 Inet4Address example2 = v4Address("192.0.2.43"); 258 259 // A packet without any addresses is not valid. 260 checkIpAddress(null, ANY, ANY, slash24Netmask); 261 262 // ClientIP is used iff YourIP is not present. 263 checkIpAddress("192.0.2.1/24", example2, example1, slash24Netmask); 264 checkIpAddress("192.0.2.43/11", example2, ANY, slash11Netmask); 265 checkIpAddress("192.0.2.43/11", ANY, example2, slash11Netmask); 266 267 // Invalid netmasks are ignored. 268 checkIpAddress(null, example2, ANY, invalidNetmask); 269 270 // If there is no netmask, implicit netmasks are used. 271 checkIpAddress("192.0.2.43/24", ANY, example2, null); 272 } 273 274 private void assertDhcpResults(String ipAddress, String gateway, String dnsServersString, 275 String domains, String serverAddress, String vendorInfo, int leaseDuration, 276 boolean hasMeteredHint, int mtu, DhcpResults dhcpResults) throws Exception { 277 assertEquals(new LinkAddress(ipAddress), dhcpResults.ipAddress); 278 assertEquals(v4Address(gateway), dhcpResults.gateway); 279 280 String[] dnsServerStrings = dnsServersString.split(","); 281 ArrayList dnsServers = new ArrayList(); 282 for (String dnsServerString : dnsServerStrings) { 283 dnsServers.add(v4Address(dnsServerString)); 284 } 285 assertEquals(dnsServers, dhcpResults.dnsServers); 286 287 assertEquals(domains, dhcpResults.domains); 288 assertEquals(v4Address(serverAddress), dhcpResults.serverAddress); 289 assertEquals(vendorInfo, dhcpResults.vendorInfo); 290 assertEquals(leaseDuration, dhcpResults.leaseDuration); 291 assertEquals(hasMeteredHint, dhcpResults.hasMeteredHint()); 292 assertEquals(mtu, dhcpResults.mtu); 293 } 294 295 @Test 296 public void testOffer1() throws Exception { 297 // TODO: Turn all of these into golden files. This will probably require using 298 // android.support.test.InstrumentationRegistry for obtaining a Context object 299 // to read such golden files, along with an appropriate Android.mk. 300 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 301 // IP header. 302 "451001480000000080118849c0a89003c0a89ff7" + 303 // UDP header. 304 "004300440134dcfa" + 305 // BOOTP header. 306 "02010600c997a63b0000000000000000c0a89ff70000000000000000" + 307 // MAC address. 308 "30766ff2a90c00000000000000000000" + 309 // Server name. 310 "0000000000000000000000000000000000000000000000000000000000000000" + 311 "0000000000000000000000000000000000000000000000000000000000000000" + 312 // File. 313 "0000000000000000000000000000000000000000000000000000000000000000" + 314 "0000000000000000000000000000000000000000000000000000000000000000" + 315 "0000000000000000000000000000000000000000000000000000000000000000" + 316 "0000000000000000000000000000000000000000000000000000000000000000" + 317 // Options 318 "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" + 319 "3a0400000e103b040000189cff00000000000000000000")); 320 321 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 322 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 323 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 324 assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", 325 null, "192.168.144.3", null, 7200, false, 0, dhcpResults); 326 } 327 328 @Test 329 public void testOffer2() throws Exception { 330 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 331 // IP header. 332 "450001518d0600004011144dc0a82b01c0a82bf7" + 333 // UDP header. 334 "00430044013d9ac7" + 335 // BOOTP header. 336 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 337 // MAC address. 338 "30766ff2a90c00000000000000000000" + 339 // Server name. 340 "0000000000000000000000000000000000000000000000000000000000000000" + 341 "0000000000000000000000000000000000000000000000000000000000000000" + 342 // File. 343 "0000000000000000000000000000000000000000000000000000000000000000" + 344 "0000000000000000000000000000000000000000000000000000000000000000" + 345 "0000000000000000000000000000000000000000000000000000000000000000" + 346 "0000000000000000000000000000000000000000000000000000000000000000" + 347 // Options 348 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 349 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff")); 350 351 assertEquals(337, packet.limit()); 352 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 353 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 354 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 355 assertDhcpResults("192.168.43.247/24", "192.168.43.1", "192.168.43.1", 356 null, "192.168.43.1", "ANDROID_METERED", 3600, true, 0, dhcpResults); 357 assertTrue(dhcpResults.hasMeteredHint()); 358 } 359 360 @Test 361 public void testBadIpPacket() throws Exception { 362 final byte[] packet = HexDump.hexStringToByteArray( 363 // IP header. 364 "450001518d0600004011144dc0a82b01c0a82bf7"); 365 366 try { 367 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 368 } catch (DhcpPacket.ParseException expected) { 369 assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); 370 return; 371 } 372 fail("Dhcp packet parsing should have failed"); 373 } 374 375 @Test 376 public void testBadDhcpPacket() throws Exception { 377 final byte[] packet = HexDump.hexStringToByteArray( 378 // IP header. 379 "450001518d0600004011144dc0a82b01c0a82bf7" + 380 // UDP header. 381 "00430044013d9ac7" + 382 // BOOTP header. 383 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000"); 384 385 try { 386 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 387 } catch (DhcpPacket.ParseException expected) { 388 assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); 389 return; 390 } 391 fail("Dhcp packet parsing should have failed"); 392 } 393 394 @Test 395 public void testBadTruncatedOffer() throws Exception { 396 final byte[] packet = HexDump.hexStringToByteArray( 397 // IP header. 398 "450001518d0600004011144dc0a82b01c0a82bf7" + 399 // UDP header. 400 "00430044013d9ac7" + 401 // BOOTP header. 402 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 403 // MAC address. 404 "30766ff2a90c00000000000000000000" + 405 // Server name. 406 "0000000000000000000000000000000000000000000000000000000000000000" + 407 "0000000000000000000000000000000000000000000000000000000000000000" + 408 // File, missing one byte 409 "0000000000000000000000000000000000000000000000000000000000000000" + 410 "0000000000000000000000000000000000000000000000000000000000000000" + 411 "0000000000000000000000000000000000000000000000000000000000000000" + 412 "00000000000000000000000000000000000000000000000000000000000000"); 413 414 try { 415 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 416 } catch (DhcpPacket.ParseException expected) { 417 assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode); 418 return; 419 } 420 fail("Dhcp packet parsing should have failed"); 421 } 422 423 @Test 424 public void testBadOfferWithoutACookie() throws Exception { 425 final byte[] packet = HexDump.hexStringToByteArray( 426 // IP header. 427 "450001518d0600004011144dc0a82b01c0a82bf7" + 428 // UDP header. 429 "00430044013d9ac7" + 430 // BOOTP header. 431 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 432 // MAC address. 433 "30766ff2a90c00000000000000000000" + 434 // Server name. 435 "0000000000000000000000000000000000000000000000000000000000000000" + 436 "0000000000000000000000000000000000000000000000000000000000000000" + 437 // File. 438 "0000000000000000000000000000000000000000000000000000000000000000" + 439 "0000000000000000000000000000000000000000000000000000000000000000" + 440 "0000000000000000000000000000000000000000000000000000000000000000" + 441 "0000000000000000000000000000000000000000000000000000000000000000" 442 // No options 443 ); 444 445 try { 446 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 447 } catch (DhcpPacket.ParseException expected) { 448 assertDhcpErrorCodes(DhcpErrorEvent.DHCP_NO_COOKIE, expected.errorCode); 449 return; 450 } 451 fail("Dhcp packet parsing should have failed"); 452 } 453 454 @Test 455 public void testOfferWithBadCookie() throws Exception { 456 final byte[] packet = HexDump.hexStringToByteArray( 457 // IP header. 458 "450001518d0600004011144dc0a82b01c0a82bf7" + 459 // UDP header. 460 "00430044013d9ac7" + 461 // BOOTP header. 462 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 463 // MAC address. 464 "30766ff2a90c00000000000000000000" + 465 // Server name. 466 "0000000000000000000000000000000000000000000000000000000000000000" + 467 "0000000000000000000000000000000000000000000000000000000000000000" + 468 // File. 469 "0000000000000000000000000000000000000000000000000000000000000000" + 470 "0000000000000000000000000000000000000000000000000000000000000000" + 471 "0000000000000000000000000000000000000000000000000000000000000000" + 472 "0000000000000000000000000000000000000000000000000000000000000000" + 473 // Bad cookie 474 "DEADBEEF3501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 475 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"); 476 477 try { 478 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 479 } catch (DhcpPacket.ParseException expected) { 480 assertDhcpErrorCodes(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE, expected.errorCode); 481 return; 482 } 483 fail("Dhcp packet parsing should have failed"); 484 } 485 486 private void assertDhcpErrorCodes(int expected, int got) { 487 assertEquals(Integer.toHexString(expected), Integer.toHexString(got)); 488 } 489 490 @Test 491 public void testTruncatedOfferPackets() throws Exception { 492 final byte[] packet = HexDump.hexStringToByteArray( 493 // IP header. 494 "450001518d0600004011144dc0a82b01c0a82bf7" + 495 // UDP header. 496 "00430044013d9ac7" + 497 // BOOTP header. 498 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 499 // MAC address. 500 "30766ff2a90c00000000000000000000" + 501 // Server name. 502 "0000000000000000000000000000000000000000000000000000000000000000" + 503 "0000000000000000000000000000000000000000000000000000000000000000" + 504 // File. 505 "0000000000000000000000000000000000000000000000000000000000000000" + 506 "0000000000000000000000000000000000000000000000000000000000000000" + 507 "0000000000000000000000000000000000000000000000000000000000000000" + 508 "0000000000000000000000000000000000000000000000000000000000000000" + 509 // Options 510 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 511 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"); 512 513 for (int len = 0; len < packet.length; len++) { 514 try { 515 DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3); 516 } catch (ParseException e) { 517 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) { 518 fail(String.format("bad truncated packet of length %d", len)); 519 } 520 } 521 } 522 } 523 524 @Test 525 public void testRandomPackets() throws Exception { 526 final int maxRandomPacketSize = 512; 527 final Random r = new Random(); 528 for (int i = 0; i < 10000; i++) { 529 byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; 530 r.nextBytes(packet); 531 try { 532 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 533 } catch (ParseException e) { 534 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) { 535 fail("bad packet: " + HexDump.toHexString(packet)); 536 } 537 } 538 } 539 } 540 541 private byte[] mtuBytes(int mtu) { 542 // 0x1a02: option 26, length 2. 0xff: no more options. 543 if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) { 544 throw new IllegalArgumentException( 545 String.format("Invalid MTU %d, must be 16-bit unsigned", mtu)); 546 } 547 String hexString = String.format("1a02%04xff", mtu); 548 return HexDump.hexStringToByteArray(hexString); 549 } 550 551 private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception { 552 if (mtuBytes != null) { 553 packet.position(packet.capacity() - mtuBytes.length); 554 packet.put(mtuBytes); 555 packet.clear(); 556 } 557 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 558 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 559 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 560 assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", 561 null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults); 562 } 563 564 @Test 565 public void testMtu() throws Exception { 566 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 567 // IP header. 568 "451001480000000080118849c0a89003c0a89ff7" + 569 // UDP header. 570 "004300440134dcfa" + 571 // BOOTP header. 572 "02010600c997a63b0000000000000000c0a89ff70000000000000000" + 573 // MAC address. 574 "30766ff2a90c00000000000000000000" + 575 // Server name. 576 "0000000000000000000000000000000000000000000000000000000000000000" + 577 "0000000000000000000000000000000000000000000000000000000000000000" + 578 // File. 579 "0000000000000000000000000000000000000000000000000000000000000000" + 580 "0000000000000000000000000000000000000000000000000000000000000000" + 581 "0000000000000000000000000000000000000000000000000000000000000000" + 582 "0000000000000000000000000000000000000000000000000000000000000000" + 583 // Options 584 "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" + 585 "3a0400000e103b040000189cff00000000")); 586 587 checkMtu(packet, 0, null); 588 checkMtu(packet, 0, mtuBytes(1501)); 589 checkMtu(packet, 1500, mtuBytes(1500)); 590 checkMtu(packet, 1499, mtuBytes(1499)); 591 checkMtu(packet, 1280, mtuBytes(1280)); 592 checkMtu(packet, 0, mtuBytes(1279)); 593 checkMtu(packet, 0, mtuBytes(576)); 594 checkMtu(packet, 0, mtuBytes(68)); 595 checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE)); 596 checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3)); 597 checkMtu(packet, 0, mtuBytes(-1)); 598 } 599 600 @Test 601 public void testBadHwaddrLength() throws Exception { 602 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 603 // IP header. 604 "450001518d0600004011144dc0a82b01c0a82bf7" + 605 // UDP header. 606 "00430044013d9ac7" + 607 // BOOTP header. 608 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 609 // MAC address. 610 "30766ff2a90c00000000000000000000" + 611 // Server name. 612 "0000000000000000000000000000000000000000000000000000000000000000" + 613 "0000000000000000000000000000000000000000000000000000000000000000" + 614 // File. 615 "0000000000000000000000000000000000000000000000000000000000000000" + 616 "0000000000000000000000000000000000000000000000000000000000000000" + 617 "0000000000000000000000000000000000000000000000000000000000000000" + 618 "0000000000000000000000000000000000000000000000000000000000000000" + 619 // Options 620 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 621 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff")); 622 String expectedClientMac = "30766FF2A90C"; 623 624 final int hwAddrLenOffset = 20 + 8 + 2; 625 assertEquals(6, packet.get(hwAddrLenOffset)); 626 627 // Expect the expected. 628 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 629 assertNotNull(offerPacket); 630 assertEquals(6, offerPacket.getClientMac().length); 631 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 632 633 // Reduce the hardware address length and verify that it shortens the client MAC. 634 packet.flip(); 635 packet.put(hwAddrLenOffset, (byte) 5); 636 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 637 assertNotNull(offerPacket); 638 assertEquals(5, offerPacket.getClientMac().length); 639 assertEquals(expectedClientMac.substring(0, 10), 640 HexDump.toHexString(offerPacket.getClientMac())); 641 642 packet.flip(); 643 packet.put(hwAddrLenOffset, (byte) 3); 644 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 645 assertNotNull(offerPacket); 646 assertEquals(3, offerPacket.getClientMac().length); 647 assertEquals(expectedClientMac.substring(0, 6), 648 HexDump.toHexString(offerPacket.getClientMac())); 649 650 // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1 651 // and crash, and b) hardcode it to 6. 652 packet.flip(); 653 packet.put(hwAddrLenOffset, (byte) -1); 654 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 655 assertNotNull(offerPacket); 656 assertEquals(6, offerPacket.getClientMac().length); 657 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 658 659 // Set the the hardware address length to a positive invalid value (> 16) and verify that we 660 // hardcode it to 6. 661 packet.flip(); 662 packet.put(hwAddrLenOffset, (byte) 17); 663 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 664 assertNotNull(offerPacket); 665 assertEquals(6, offerPacket.getClientMac().length); 666 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 667 } 668 669 @Test 670 public void testPadAndOverloadedOptionsOffer() throws Exception { 671 // A packet observed in the real world that is interesting for two reasons: 672 // 673 // 1. It uses pad bytes, which we previously didn't support correctly. 674 // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't 675 // store any information in the overloaded fields). 676 // 677 // For now, we just check that it parses correctly. 678 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 679 // Ethernet header. 680 "b4cef6000000e80462236e300800" + 681 // IP header. 682 "4500014c00000000ff11741701010101ac119876" + 683 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 684 "004300440138ae5a" + 685 // BOOTP header. 686 "020106000fa0059f0000000000000000ac1198760000000000000000" + 687 // MAC address. 688 "b4cef600000000000000000000000000" + 689 // Server name. 690 "ff00000000000000000000000000000000000000000000000000000000000000" + 691 "0000000000000000000000000000000000000000000000000000000000000000" + 692 // File. 693 "ff00000000000000000000000000000000000000000000000000000000000000" + 694 "0000000000000000000000000000000000000000000000000000000000000000" + 695 "0000000000000000000000000000000000000000000000000000000000000000" + 696 "0000000000000000000000000000000000000000000000000000000000000000" + 697 // Options 698 "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" + 699 "0000000000000000000000000000000000000000000000ff000000")); 700 701 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 702 assertTrue(offerPacket instanceof DhcpOfferPacket); 703 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 704 assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1", 705 null, "1.1.1.1", null, 43200, false, 0, dhcpResults); 706 } 707 708 @Test 709 public void testBug2111() throws Exception { 710 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 711 // IP header. 712 "4500014c00000000ff119beac3eaf3880a3f5d04" + 713 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 714 "0043004401387464" + 715 // BOOTP header. 716 "0201060002554812000a0000000000000a3f5d040000000000000000" + 717 // MAC address. 718 "00904c00000000000000000000000000" + 719 // Server name. 720 "0000000000000000000000000000000000000000000000000000000000000000" + 721 "0000000000000000000000000000000000000000000000000000000000000000" + 722 // File. 723 "0000000000000000000000000000000000000000000000000000000000000000" + 724 "0000000000000000000000000000000000000000000000000000000000000000" + 725 "0000000000000000000000000000000000000000000000000000000000000000" + 726 "0000000000000000000000000000000000000000000000000000000000000000" + 727 // Options. 728 "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" + 729 "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000")); 730 731 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 732 assertTrue(offerPacket instanceof DhcpOfferPacket); 733 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 734 assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2", 735 "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults); 736 } 737 738 @Test 739 public void testBug2136() throws Exception { 740 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 741 // Ethernet header. 742 "bcf5ac000000d0c7890000000800" + 743 // IP header. 744 "4500014c00000000ff119beac3eaf3880a3f5d04" + 745 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 746 "0043004401387574" + 747 // BOOTP header. 748 "0201060163339a3000050000000000000a209ecd0000000000000000" + 749 // MAC address. 750 "bcf5ac00000000000000000000000000" + 751 // Server name. 752 "0000000000000000000000000000000000000000000000000000000000000000" + 753 "0000000000000000000000000000000000000000000000000000000000000000" + 754 // File. 755 "0000000000000000000000000000000000000000000000000000000000000000" + 756 "0000000000000000000000000000000000000000000000000000000000000000" + 757 "0000000000000000000000000000000000000000000000000000000000000000" + 758 "0000000000000000000000000000000000000000000000000000000000000000" + 759 // Options. 760 "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" + 761 "0f0b6c616e63732e61632e756b000000000000000000ff00000000")); 762 763 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 764 assertTrue(offerPacket instanceof DhcpOfferPacket); 765 assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac())); 766 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 767 assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53", 768 "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults); 769 } 770 771 @Test 772 public void testUdpServerAnySourcePort() throws Exception { 773 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 774 // Ethernet header. 775 "9cd917000000001c2e0000000800" + 776 // IP header. 777 "45a00148000040003d115087d18194fb0a0f7af2" + 778 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 779 // NOTE: The server source port is not the canonical port 67. 780 "C29F004401341268" + 781 // BOOTP header. 782 "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + 783 // MAC address. 784 "9cd91700000000000000000000000000" + 785 // Server name. 786 "0000000000000000000000000000000000000000000000000000000000000000" + 787 "0000000000000000000000000000000000000000000000000000000000000000" + 788 // File. 789 "0000000000000000000000000000000000000000000000000000000000000000" + 790 "0000000000000000000000000000000000000000000000000000000000000000" + 791 "0000000000000000000000000000000000000000000000000000000000000000" + 792 "0000000000000000000000000000000000000000000000000000000000000000" + 793 // Options. 794 "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + 795 "d18180060f0777766d2e6564751c040a0fffffff000000")); 796 797 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 798 assertTrue(offerPacket instanceof DhcpOfferPacket); 799 assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac())); 800 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 801 assertDhcpResults("10.15.122.242/16", "10.15.200.23", 802 "209.129.128.3,209.129.148.3,209.129.128.6", 803 "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults); 804 } 805 806 @Test 807 public void testUdpInvalidDstPort() throws Exception { 808 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 809 // Ethernet header. 810 "9cd917000000001c2e0000000800" + 811 // IP header. 812 "45a00148000040003d115087d18194fb0a0f7af2" + 813 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 814 // NOTE: The destination port is a non-DHCP port. 815 "0043aaaa01341268" + 816 // BOOTP header. 817 "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + 818 // MAC address. 819 "9cd91700000000000000000000000000" + 820 // Server name. 821 "0000000000000000000000000000000000000000000000000000000000000000" + 822 "0000000000000000000000000000000000000000000000000000000000000000" + 823 // File. 824 "0000000000000000000000000000000000000000000000000000000000000000" + 825 "0000000000000000000000000000000000000000000000000000000000000000" + 826 "0000000000000000000000000000000000000000000000000000000000000000" + 827 "0000000000000000000000000000000000000000000000000000000000000000" + 828 // Options. 829 "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + 830 "d18180060f0777766d2e6564751c040a0fffffff000000")); 831 832 try { 833 DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 834 fail("Packet with invalid dst port did not throw ParseException"); 835 } catch (ParseException expected) {} 836 } 837 838 @Test 839 public void testMultipleRouters() throws Exception { 840 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 841 // Ethernet header. 842 "fc3d93000000" + "081735000000" + "0800" + 843 // IP header. 844 "45000148c2370000ff117ac2c0a8bd02ffffffff" + 845 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 846 "0043004401343beb" + 847 // BOOTP header. 848 "0201060027f518e20000800000000000c0a8bd310000000000000000" + 849 // MAC address. 850 "fc3d9300000000000000000000000000" + 851 // Server name. 852 "0000000000000000000000000000000000000000000000000000000000000000" + 853 "0000000000000000000000000000000000000000000000000000000000000000" + 854 // File. 855 "0000000000000000000000000000000000000000000000000000000000000000" + 856 "0000000000000000000000000000000000000000000000000000000000000000" + 857 "0000000000000000000000000000000000000000000000000000000000000000" + 858 "0000000000000000000000000000000000000000000000000000000000000000" + 859 // Options. 860 "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" + 861 "0308c0a8bd01ffffff0006080808080808080404ff000000000000")); 862 863 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 864 assertTrue(offerPacket instanceof DhcpOfferPacket); 865 assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac())); 866 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 867 assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4", 868 null, "192.171.189.2", null, 28800, false, 0, dhcpResults); 869 } 870 871 @Test 872 public void testDiscoverPacket() throws Exception { 873 short secs = 7; 874 int transactionId = 0xdeadbeef; 875 byte[] hwaddr = { 876 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a 877 }; 878 879 ByteBuffer packet = DhcpPacket.buildDiscoverPacket( 880 DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr, 881 false /* do unicast */, DhcpClient.REQUESTED_PARAMS); 882 883 byte[] headers = new byte[] { 884 // Ethernet header. 885 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 886 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, 887 (byte) 0x08, (byte) 0x00, 888 // IP header. 889 (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56, 890 (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00, 891 (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88, 892 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 893 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 894 // UDP header. 895 (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43, 896 (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a, 897 // BOOTP. 898 (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00, 899 (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, 900 (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00, 901 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 902 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 903 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 904 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 905 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, 906 (byte) 0xb1, (byte) 0x7a 907 }; 908 byte[] options = new byte[] { 909 // Magic cookie 0x63825363. 910 (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63, 911 // Message type DISCOVER. 912 (byte) 0x35, (byte) 0x01, (byte) 0x01, 913 // Client identifier Ethernet, da:01:19:5b:b1:7a. 914 (byte) 0x3d, (byte) 0x07, 915 (byte) 0x01, 916 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, 917 // Max message size 1500. 918 (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc, 919 // Version "android-dhcp-???". 920 (byte) 0x3c, (byte) 0x10, 921 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?', 922 // Hostname "android-01234567890abcde" 923 (byte) 0x0c, (byte) 0x18, 924 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 925 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 926 // Requested parameter list. 927 (byte) 0x37, (byte) 0x0a, 928 DHCP_SUBNET_MASK, 929 DHCP_ROUTER, 930 DHCP_DNS_SERVER, 931 DHCP_DOMAIN_NAME, 932 DHCP_MTU, 933 DHCP_BROADCAST_ADDRESS, 934 DHCP_LEASE_TIME, 935 DHCP_RENEWAL_TIME, 936 DHCP_REBINDING_TIME, 937 DHCP_VENDOR_INFO, 938 // End options. 939 (byte) 0xff, 940 // Our packets are always of even length. TODO: find out why and possibly fix it. 941 (byte) 0x00 942 }; 943 byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length]; 944 assertTrue((expected.length & 1) == 0); 945 System.arraycopy(headers, 0, expected, 0, headers.length); 946 System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length); 947 948 byte[] actual = new byte[packet.limit()]; 949 packet.get(actual); 950 String msg = 951 "Expected:\n " + Arrays.toString(expected) + 952 "\nActual:\n " + Arrays.toString(actual); 953 assertTrue(msg, Arrays.equals(expected, actual)); 954 } 955} 956