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