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