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