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