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