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