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 public void testTruncatedOfferPackets() throws Exception { 477 final byte[] packet = HexDump.hexStringToByteArray( 478 // IP header. 479 "450001518d0600004011144dc0a82b01c0a82bf7" + 480 // UDP header. 481 "00430044013d9ac7" + 482 // BOOTP header. 483 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 484 // MAC address. 485 "30766ff2a90c00000000000000000000" + 486 // Server name. 487 "0000000000000000000000000000000000000000000000000000000000000000" + 488 "0000000000000000000000000000000000000000000000000000000000000000" + 489 // File. 490 "0000000000000000000000000000000000000000000000000000000000000000" + 491 "0000000000000000000000000000000000000000000000000000000000000000" + 492 "0000000000000000000000000000000000000000000000000000000000000000" + 493 "0000000000000000000000000000000000000000000000000000000000000000" + 494 // Options 495 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 496 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"); 497 498 for (int len = 0; len < packet.length; len++) { 499 try { 500 DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3); 501 } catch (ParseException e) { 502 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) { 503 fail(String.format("bad truncated packet of length %d", len)); 504 } 505 } 506 } 507 } 508 509 public void testRandomPackets() throws Exception { 510 final int maxRandomPacketSize = 512; 511 final Random r = new Random(); 512 for (int i = 0; i < 10000; i++) { 513 byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; 514 r.nextBytes(packet); 515 try { 516 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3); 517 } catch (ParseException e) { 518 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) { 519 fail("bad packet: " + HexDump.toHexString(packet)); 520 } 521 } 522 } 523 } 524 525 private byte[] mtuBytes(int mtu) { 526 // 0x1a02: option 26, length 2. 0xff: no more options. 527 if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) { 528 throw new IllegalArgumentException( 529 String.format("Invalid MTU %d, must be 16-bit unsigned", mtu)); 530 } 531 String hexString = String.format("1a02%04xff", mtu); 532 return HexDump.hexStringToByteArray(hexString); 533 } 534 535 private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception { 536 if (mtuBytes != null) { 537 packet.position(packet.capacity() - mtuBytes.length); 538 packet.put(mtuBytes); 539 packet.clear(); 540 } 541 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 542 assertTrue(offerPacket instanceof DhcpOfferPacket); // Implicitly checks it's non-null. 543 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 544 assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4", 545 null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults); 546 } 547 548 @SmallTest 549 public void testMtu() throws Exception { 550 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 551 // IP header. 552 "451001480000000080118849c0a89003c0a89ff7" + 553 // UDP header. 554 "004300440134dcfa" + 555 // BOOTP header. 556 "02010600c997a63b0000000000000000c0a89ff70000000000000000" + 557 // MAC address. 558 "30766ff2a90c00000000000000000000" + 559 // Server name. 560 "0000000000000000000000000000000000000000000000000000000000000000" + 561 "0000000000000000000000000000000000000000000000000000000000000000" + 562 // File. 563 "0000000000000000000000000000000000000000000000000000000000000000" + 564 "0000000000000000000000000000000000000000000000000000000000000000" + 565 "0000000000000000000000000000000000000000000000000000000000000000" + 566 "0000000000000000000000000000000000000000000000000000000000000000" + 567 // Options 568 "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" + 569 "3a0400000e103b040000189cff00000000")); 570 571 checkMtu(packet, 0, null); 572 checkMtu(packet, 0, mtuBytes(1501)); 573 checkMtu(packet, 1500, mtuBytes(1500)); 574 checkMtu(packet, 1499, mtuBytes(1499)); 575 checkMtu(packet, 1280, mtuBytes(1280)); 576 checkMtu(packet, 0, mtuBytes(1279)); 577 checkMtu(packet, 0, mtuBytes(576)); 578 checkMtu(packet, 0, mtuBytes(68)); 579 checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE)); 580 checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3)); 581 checkMtu(packet, 0, mtuBytes(-1)); 582 } 583 584 @SmallTest 585 public void testBadHwaddrLength() throws Exception { 586 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 587 // IP header. 588 "450001518d0600004011144dc0a82b01c0a82bf7" + 589 // UDP header. 590 "00430044013d9ac7" + 591 // BOOTP header. 592 "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" + 593 // MAC address. 594 "30766ff2a90c00000000000000000000" + 595 // Server name. 596 "0000000000000000000000000000000000000000000000000000000000000000" + 597 "0000000000000000000000000000000000000000000000000000000000000000" + 598 // File. 599 "0000000000000000000000000000000000000000000000000000000000000000" + 600 "0000000000000000000000000000000000000000000000000000000000000000" + 601 "0000000000000000000000000000000000000000000000000000000000000000" + 602 "0000000000000000000000000000000000000000000000000000000000000000" + 603 // Options 604 "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" + 605 "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff")); 606 String expectedClientMac = "30766FF2A90C"; 607 608 final int hwAddrLenOffset = 20 + 8 + 2; 609 assertEquals(6, packet.get(hwAddrLenOffset)); 610 611 // Expect the expected. 612 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 613 assertNotNull(offerPacket); 614 assertEquals(6, offerPacket.getClientMac().length); 615 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 616 617 // Reduce the hardware address length and verify that it shortens the client MAC. 618 packet.flip(); 619 packet.put(hwAddrLenOffset, (byte) 5); 620 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 621 assertNotNull(offerPacket); 622 assertEquals(5, offerPacket.getClientMac().length); 623 assertEquals(expectedClientMac.substring(0, 10), 624 HexDump.toHexString(offerPacket.getClientMac())); 625 626 packet.flip(); 627 packet.put(hwAddrLenOffset, (byte) 3); 628 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 629 assertNotNull(offerPacket); 630 assertEquals(3, offerPacket.getClientMac().length); 631 assertEquals(expectedClientMac.substring(0, 6), 632 HexDump.toHexString(offerPacket.getClientMac())); 633 634 // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1 635 // and crash, and b) hardcode it to 6. 636 packet.flip(); 637 packet.put(hwAddrLenOffset, (byte) -1); 638 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 639 assertNotNull(offerPacket); 640 assertEquals(6, offerPacket.getClientMac().length); 641 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 642 643 // Set the the hardware address length to a positive invalid value (> 16) and verify that we 644 // hardcode it to 6. 645 packet.flip(); 646 packet.put(hwAddrLenOffset, (byte) 17); 647 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 648 assertNotNull(offerPacket); 649 assertEquals(6, offerPacket.getClientMac().length); 650 assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac())); 651 } 652 653 @SmallTest 654 public void testPadAndOverloadedOptionsOffer() throws Exception { 655 // A packet observed in the real world that is interesting for two reasons: 656 // 657 // 1. It uses pad bytes, which we previously didn't support correctly. 658 // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't 659 // store any information in the overloaded fields). 660 // 661 // For now, we just check that it parses correctly. 662 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 663 // Ethernet header. 664 "b4cef6000000e80462236e300800" + 665 // IP header. 666 "4500014c00000000ff11741701010101ac119876" + 667 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 668 "004300440138ae5a" + 669 // BOOTP header. 670 "020106000fa0059f0000000000000000ac1198760000000000000000" + 671 // MAC address. 672 "b4cef600000000000000000000000000" + 673 // Server name. 674 "ff00000000000000000000000000000000000000000000000000000000000000" + 675 "0000000000000000000000000000000000000000000000000000000000000000" + 676 // File. 677 "ff00000000000000000000000000000000000000000000000000000000000000" + 678 "0000000000000000000000000000000000000000000000000000000000000000" + 679 "0000000000000000000000000000000000000000000000000000000000000000" + 680 "0000000000000000000000000000000000000000000000000000000000000000" + 681 // Options 682 "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" + 683 "0000000000000000000000000000000000000000000000ff000000")); 684 685 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 686 assertTrue(offerPacket instanceof DhcpOfferPacket); 687 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 688 assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1", 689 null, "1.1.1.1", null, 43200, false, 0, dhcpResults); 690 } 691 692 @SmallTest 693 public void testBug2111() throws Exception { 694 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 695 // IP header. 696 "4500014c00000000ff119beac3eaf3880a3f5d04" + 697 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 698 "0043004401387464" + 699 // BOOTP header. 700 "0201060002554812000a0000000000000a3f5d040000000000000000" + 701 // MAC address. 702 "00904c00000000000000000000000000" + 703 // Server name. 704 "0000000000000000000000000000000000000000000000000000000000000000" + 705 "0000000000000000000000000000000000000000000000000000000000000000" + 706 // File. 707 "0000000000000000000000000000000000000000000000000000000000000000" + 708 "0000000000000000000000000000000000000000000000000000000000000000" + 709 "0000000000000000000000000000000000000000000000000000000000000000" + 710 "0000000000000000000000000000000000000000000000000000000000000000" + 711 // Options. 712 "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" + 713 "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000")); 714 715 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3); 716 assertTrue(offerPacket instanceof DhcpOfferPacket); 717 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 718 assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2", 719 "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults); 720 } 721 722 @SmallTest 723 public void testBug2136() throws Exception { 724 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 725 // Ethernet header. 726 "bcf5ac000000d0c7890000000800" + 727 // IP header. 728 "4500014c00000000ff119beac3eaf3880a3f5d04" + 729 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 730 "0043004401387574" + 731 // BOOTP header. 732 "0201060163339a3000050000000000000a209ecd0000000000000000" + 733 // MAC address. 734 "bcf5ac00000000000000000000000000" + 735 // Server name. 736 "0000000000000000000000000000000000000000000000000000000000000000" + 737 "0000000000000000000000000000000000000000000000000000000000000000" + 738 // File. 739 "0000000000000000000000000000000000000000000000000000000000000000" + 740 "0000000000000000000000000000000000000000000000000000000000000000" + 741 "0000000000000000000000000000000000000000000000000000000000000000" + 742 "0000000000000000000000000000000000000000000000000000000000000000" + 743 // Options. 744 "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" + 745 "0f0b6c616e63732e61632e756b000000000000000000ff00000000")); 746 747 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 748 assertTrue(offerPacket instanceof DhcpOfferPacket); 749 assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac())); 750 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 751 assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53", 752 "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults); 753 } 754 755 @SmallTest 756 public void testUdpServerAnySourcePort() throws Exception { 757 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 758 // Ethernet header. 759 "9cd917000000001c2e0000000800" + 760 // IP header. 761 "45a00148000040003d115087d18194fb0a0f7af2" + 762 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 763 // NOTE: The server source port is not the canonical port 67. 764 "C29F004401341268" + 765 // BOOTP header. 766 "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + 767 // MAC address. 768 "9cd91700000000000000000000000000" + 769 // Server name. 770 "0000000000000000000000000000000000000000000000000000000000000000" + 771 "0000000000000000000000000000000000000000000000000000000000000000" + 772 // File. 773 "0000000000000000000000000000000000000000000000000000000000000000" + 774 "0000000000000000000000000000000000000000000000000000000000000000" + 775 "0000000000000000000000000000000000000000000000000000000000000000" + 776 "0000000000000000000000000000000000000000000000000000000000000000" + 777 // Options. 778 "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + 779 "d18180060f0777766d2e6564751c040a0fffffff000000")); 780 781 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 782 assertTrue(offerPacket instanceof DhcpOfferPacket); 783 assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac())); 784 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 785 assertDhcpResults("10.15.122.242/16", "10.15.200.23", 786 "209.129.128.3,209.129.148.3,209.129.128.6", 787 "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults); 788 } 789 790 @SmallTest 791 public void testUdpInvalidDstPort() throws Exception { 792 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 793 // Ethernet header. 794 "9cd917000000001c2e0000000800" + 795 // IP header. 796 "45a00148000040003d115087d18194fb0a0f7af2" + 797 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 798 // NOTE: The destination port is a non-DHCP port. 799 "0043aaaa01341268" + 800 // BOOTP header. 801 "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + 802 // MAC address. 803 "9cd91700000000000000000000000000" + 804 // Server name. 805 "0000000000000000000000000000000000000000000000000000000000000000" + 806 "0000000000000000000000000000000000000000000000000000000000000000" + 807 // File. 808 "0000000000000000000000000000000000000000000000000000000000000000" + 809 "0000000000000000000000000000000000000000000000000000000000000000" + 810 "0000000000000000000000000000000000000000000000000000000000000000" + 811 "0000000000000000000000000000000000000000000000000000000000000000" + 812 // Options. 813 "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + 814 "d18180060f0777766d2e6564751c040a0fffffff000000")); 815 816 try { 817 DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 818 fail("Packet with invalid dst port did not throw ParseException"); 819 } catch (ParseException expected) {} 820 } 821 822 @SmallTest 823 public void testMultipleRouters() throws Exception { 824 final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray( 825 // Ethernet header. 826 "fc3d93000000" + "081735000000" + "0800" + 827 // IP header. 828 "45000148c2370000ff117ac2c0a8bd02ffffffff" + 829 // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). 830 "0043004401343beb" + 831 // BOOTP header. 832 "0201060027f518e20000800000000000c0a8bd310000000000000000" + 833 // MAC address. 834 "fc3d9300000000000000000000000000" + 835 // Server name. 836 "0000000000000000000000000000000000000000000000000000000000000000" + 837 "0000000000000000000000000000000000000000000000000000000000000000" + 838 // File. 839 "0000000000000000000000000000000000000000000000000000000000000000" + 840 "0000000000000000000000000000000000000000000000000000000000000000" + 841 "0000000000000000000000000000000000000000000000000000000000000000" + 842 "0000000000000000000000000000000000000000000000000000000000000000" + 843 // Options. 844 "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" + 845 "0308c0a8bd01ffffff0006080808080808080404ff000000000000")); 846 847 DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); 848 assertTrue(offerPacket instanceof DhcpOfferPacket); 849 assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac())); 850 DhcpResults dhcpResults = offerPacket.toDhcpResults(); 851 assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4", 852 null, "192.171.189.2", null, 28800, false, 0, dhcpResults); 853 } 854 855 @SmallTest 856 public void testDiscoverPacket() throws Exception { 857 short secs = 7; 858 int transactionId = 0xdeadbeef; 859 byte[] hwaddr = { 860 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a 861 }; 862 863 ByteBuffer packet = DhcpPacket.buildDiscoverPacket( 864 DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr, 865 false /* do unicast */, DhcpClient.REQUESTED_PARAMS); 866 867 byte[] headers = new byte[] { 868 // Ethernet header. 869 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 870 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, 871 (byte) 0x08, (byte) 0x00, 872 // IP header. 873 (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56, 874 (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00, 875 (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88, 876 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 877 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 878 // UDP header. 879 (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43, 880 (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a, 881 // BOOTP. 882 (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00, 883 (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, 884 (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00, 885 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 886 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 887 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 888 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 889 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, 890 (byte) 0xb1, (byte) 0x7a 891 }; 892 byte[] options = new byte[] { 893 // Magic cookie 0x63825363. 894 (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63, 895 // Message type DISCOVER. 896 (byte) 0x35, (byte) 0x01, (byte) 0x01, 897 // Client identifier Ethernet, da:01:19:5b:b1:7a. 898 (byte) 0x3d, (byte) 0x07, 899 (byte) 0x01, 900 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a, 901 // Max message size 1500. 902 (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc, 903 // Version "android-dhcp-???". 904 (byte) 0x3c, (byte) 0x10, 905 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?', 906 // Hostname "android-01234567890abcde" 907 (byte) 0x0c, (byte) 0x18, 908 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 909 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 910 // Requested parameter list. 911 (byte) 0x37, (byte) 0x0a, 912 DHCP_SUBNET_MASK, 913 DHCP_ROUTER, 914 DHCP_DNS_SERVER, 915 DHCP_DOMAIN_NAME, 916 DHCP_MTU, 917 DHCP_BROADCAST_ADDRESS, 918 DHCP_LEASE_TIME, 919 DHCP_RENEWAL_TIME, 920 DHCP_REBINDING_TIME, 921 DHCP_VENDOR_INFO, 922 // End options. 923 (byte) 0xff, 924 // Our packets are always of even length. TODO: find out why and possibly fix it. 925 (byte) 0x00 926 }; 927 byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length]; 928 assertTrue((expected.length & 1) == 0); 929 System.arraycopy(headers, 0, expected, 0, headers.length); 930 System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length); 931 932 byte[] actual = new byte[packet.limit()]; 933 packet.get(actual); 934 String msg = 935 "Expected:\n " + Arrays.toString(expected) + 936 "\nActual:\n " + Arrays.toString(actual); 937 assertTrue(msg, Arrays.equals(expected, actual)); 938 } 939 } 940