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