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    @SmallTest
477    public void testTruncatedOfferPackets() throws Exception {
478        final byte[] packet = HexDump.hexStringToByteArray(
479            // IP header.
480            "450001518d0600004011144dc0a82b01c0a82bf7" +
481            // UDP header.
482            "00430044013d9ac7" +
483            // BOOTP header.
484            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
485            // MAC address.
486            "30766ff2a90c00000000000000000000" +
487            // Server name.
488            "0000000000000000000000000000000000000000000000000000000000000000" +
489            "0000000000000000000000000000000000000000000000000000000000000000" +
490            // File.
491            "0000000000000000000000000000000000000000000000000000000000000000" +
492            "0000000000000000000000000000000000000000000000000000000000000000" +
493            "0000000000000000000000000000000000000000000000000000000000000000" +
494            "0000000000000000000000000000000000000000000000000000000000000000" +
495            // Options
496            "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
497            "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
498
499        for (int len = 0; len < packet.length; len++) {
500            try {
501                DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3);
502            } catch (ParseException e) {
503                if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
504                    fail(String.format("bad truncated packet of length %d", len));
505                }
506            }
507        }
508    }
509
510    @SmallTest
511    public void testRandomPackets() throws Exception {
512        final int maxRandomPacketSize = 512;
513        final Random r = new Random();
514        for (int i = 0; i < 10000; i++) {
515            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
516            r.nextBytes(packet);
517            try {
518                DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
519            } catch (ParseException e) {
520                if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
521                    fail("bad packet: " + HexDump.toHexString(packet));
522                }
523            }
524        }
525    }
526
527    private byte[] mtuBytes(int mtu) {
528        // 0x1a02: option 26, length 2. 0xff: no more options.
529        if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) {
530            throw new IllegalArgumentException(
531                String.format("Invalid MTU %d, must be 16-bit unsigned", mtu));
532        }
533        String hexString = String.format("1a02%04xff", mtu);
534        return HexDump.hexStringToByteArray(hexString);
535    }
536
537    private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception {
538        if (mtuBytes != null) {
539            packet.position(packet.capacity() - mtuBytes.length);
540            packet.put(mtuBytes);
541            packet.clear();
542        }
543        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
544        assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
545        DhcpResults dhcpResults = offerPacket.toDhcpResults();
546        assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
547                null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults);
548    }
549
550    @SmallTest
551    public void testMtu() throws Exception {
552        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
553            // IP header.
554            "451001480000000080118849c0a89003c0a89ff7" +
555            // UDP header.
556            "004300440134dcfa" +
557            // BOOTP header.
558            "02010600c997a63b0000000000000000c0a89ff70000000000000000" +
559            // MAC address.
560            "30766ff2a90c00000000000000000000" +
561            // Server name.
562            "0000000000000000000000000000000000000000000000000000000000000000" +
563            "0000000000000000000000000000000000000000000000000000000000000000" +
564            // File.
565            "0000000000000000000000000000000000000000000000000000000000000000" +
566            "0000000000000000000000000000000000000000000000000000000000000000" +
567            "0000000000000000000000000000000000000000000000000000000000000000" +
568            "0000000000000000000000000000000000000000000000000000000000000000" +
569            // Options
570            "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
571            "3a0400000e103b040000189cff00000000"));
572
573        checkMtu(packet, 0, null);
574        checkMtu(packet, 0, mtuBytes(1501));
575        checkMtu(packet, 1500, mtuBytes(1500));
576        checkMtu(packet, 1499, mtuBytes(1499));
577        checkMtu(packet, 1280, mtuBytes(1280));
578        checkMtu(packet, 0, mtuBytes(1279));
579        checkMtu(packet, 0, mtuBytes(576));
580        checkMtu(packet, 0, mtuBytes(68));
581        checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE));
582        checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3));
583        checkMtu(packet, 0, mtuBytes(-1));
584    }
585
586    @SmallTest
587    public void testBadHwaddrLength() throws Exception {
588        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
589            // IP header.
590            "450001518d0600004011144dc0a82b01c0a82bf7" +
591            // UDP header.
592            "00430044013d9ac7" +
593            // BOOTP header.
594            "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
595            // MAC address.
596            "30766ff2a90c00000000000000000000" +
597            // Server name.
598            "0000000000000000000000000000000000000000000000000000000000000000" +
599            "0000000000000000000000000000000000000000000000000000000000000000" +
600            // File.
601            "0000000000000000000000000000000000000000000000000000000000000000" +
602            "0000000000000000000000000000000000000000000000000000000000000000" +
603            "0000000000000000000000000000000000000000000000000000000000000000" +
604            "0000000000000000000000000000000000000000000000000000000000000000" +
605            // Options
606            "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
607            "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
608        String expectedClientMac = "30766FF2A90C";
609
610        final int hwAddrLenOffset = 20 + 8 + 2;
611        assertEquals(6, packet.get(hwAddrLenOffset));
612
613        // Expect the expected.
614        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
615        assertNotNull(offerPacket);
616        assertEquals(6, offerPacket.getClientMac().length);
617        assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
618
619        // Reduce the hardware address length and verify that it shortens the client MAC.
620        packet.flip();
621        packet.put(hwAddrLenOffset, (byte) 5);
622        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
623        assertNotNull(offerPacket);
624        assertEquals(5, offerPacket.getClientMac().length);
625        assertEquals(expectedClientMac.substring(0, 10),
626                HexDump.toHexString(offerPacket.getClientMac()));
627
628        packet.flip();
629        packet.put(hwAddrLenOffset, (byte) 3);
630        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
631        assertNotNull(offerPacket);
632        assertEquals(3, offerPacket.getClientMac().length);
633        assertEquals(expectedClientMac.substring(0, 6),
634                HexDump.toHexString(offerPacket.getClientMac()));
635
636        // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1
637        // and crash, and b) hardcode it to 6.
638        packet.flip();
639        packet.put(hwAddrLenOffset, (byte) -1);
640        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
641        assertNotNull(offerPacket);
642        assertEquals(6, offerPacket.getClientMac().length);
643        assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
644
645        // Set the the hardware address length to a positive invalid value (> 16) and verify that we
646        // hardcode it to 6.
647        packet.flip();
648        packet.put(hwAddrLenOffset, (byte) 17);
649        offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
650        assertNotNull(offerPacket);
651        assertEquals(6, offerPacket.getClientMac().length);
652        assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
653    }
654
655    @SmallTest
656    public void testPadAndOverloadedOptionsOffer() throws Exception {
657        // A packet observed in the real world that is interesting for two reasons:
658        //
659        // 1. It uses pad bytes, which we previously didn't support correctly.
660        // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't
661        //    store any information in the overloaded fields).
662        //
663        // For now, we just check that it parses correctly.
664        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
665            // Ethernet header.
666            "b4cef6000000e80462236e300800" +
667            // IP header.
668            "4500014c00000000ff11741701010101ac119876" +
669            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
670            "004300440138ae5a" +
671            // BOOTP header.
672            "020106000fa0059f0000000000000000ac1198760000000000000000" +
673            // MAC address.
674            "b4cef600000000000000000000000000" +
675            // Server name.
676            "ff00000000000000000000000000000000000000000000000000000000000000" +
677            "0000000000000000000000000000000000000000000000000000000000000000" +
678            // File.
679            "ff00000000000000000000000000000000000000000000000000000000000000" +
680            "0000000000000000000000000000000000000000000000000000000000000000" +
681            "0000000000000000000000000000000000000000000000000000000000000000" +
682            "0000000000000000000000000000000000000000000000000000000000000000" +
683            // Options
684            "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" +
685            "0000000000000000000000000000000000000000000000ff000000"));
686
687        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
688        assertTrue(offerPacket instanceof DhcpOfferPacket);
689        DhcpResults dhcpResults = offerPacket.toDhcpResults();
690        assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1",
691                null, "1.1.1.1", null, 43200, false, 0, dhcpResults);
692    }
693
694    @SmallTest
695    public void testBug2111() throws Exception {
696        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
697            // IP header.
698            "4500014c00000000ff119beac3eaf3880a3f5d04" +
699            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
700            "0043004401387464" +
701            // BOOTP header.
702            "0201060002554812000a0000000000000a3f5d040000000000000000" +
703            // MAC address.
704            "00904c00000000000000000000000000" +
705            // Server name.
706            "0000000000000000000000000000000000000000000000000000000000000000" +
707            "0000000000000000000000000000000000000000000000000000000000000000" +
708            // File.
709            "0000000000000000000000000000000000000000000000000000000000000000" +
710            "0000000000000000000000000000000000000000000000000000000000000000" +
711            "0000000000000000000000000000000000000000000000000000000000000000" +
712            "0000000000000000000000000000000000000000000000000000000000000000" +
713            // Options.
714            "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" +
715            "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"));
716
717        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
718        assertTrue(offerPacket instanceof DhcpOfferPacket);
719        DhcpResults dhcpResults = offerPacket.toDhcpResults();
720        assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2",
721                "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults);
722    }
723
724    @SmallTest
725    public void testBug2136() throws Exception {
726        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
727            // Ethernet header.
728            "bcf5ac000000d0c7890000000800" +
729            // IP header.
730            "4500014c00000000ff119beac3eaf3880a3f5d04" +
731            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
732            "0043004401387574" +
733            // BOOTP header.
734            "0201060163339a3000050000000000000a209ecd0000000000000000" +
735            // MAC address.
736            "bcf5ac00000000000000000000000000" +
737            // Server name.
738            "0000000000000000000000000000000000000000000000000000000000000000" +
739            "0000000000000000000000000000000000000000000000000000000000000000" +
740            // File.
741            "0000000000000000000000000000000000000000000000000000000000000000" +
742            "0000000000000000000000000000000000000000000000000000000000000000" +
743            "0000000000000000000000000000000000000000000000000000000000000000" +
744            "0000000000000000000000000000000000000000000000000000000000000000" +
745            // Options.
746            "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" +
747            "0f0b6c616e63732e61632e756b000000000000000000ff00000000"));
748
749        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
750        assertTrue(offerPacket instanceof DhcpOfferPacket);
751        assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac()));
752        DhcpResults dhcpResults = offerPacket.toDhcpResults();
753        assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
754                "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults);
755    }
756
757    @SmallTest
758    public void testUdpServerAnySourcePort() throws Exception {
759        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
760            // Ethernet header.
761            "9cd917000000001c2e0000000800" +
762            // IP header.
763            "45a00148000040003d115087d18194fb0a0f7af2" +
764            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
765            // NOTE: The server source port is not the canonical port 67.
766            "C29F004401341268" +
767            // BOOTP header.
768            "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
769            // MAC address.
770            "9cd91700000000000000000000000000" +
771            // Server name.
772            "0000000000000000000000000000000000000000000000000000000000000000" +
773            "0000000000000000000000000000000000000000000000000000000000000000" +
774            // File.
775            "0000000000000000000000000000000000000000000000000000000000000000" +
776            "0000000000000000000000000000000000000000000000000000000000000000" +
777            "0000000000000000000000000000000000000000000000000000000000000000" +
778            "0000000000000000000000000000000000000000000000000000000000000000" +
779            // Options.
780            "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
781            "d18180060f0777766d2e6564751c040a0fffffff000000"));
782
783        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
784        assertTrue(offerPacket instanceof DhcpOfferPacket);
785        assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac()));
786        DhcpResults dhcpResults = offerPacket.toDhcpResults();
787        assertDhcpResults("10.15.122.242/16", "10.15.200.23",
788                "209.129.128.3,209.129.148.3,209.129.128.6",
789                "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults);
790    }
791
792    @SmallTest
793    public void testUdpInvalidDstPort() throws Exception {
794        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
795            // Ethernet header.
796            "9cd917000000001c2e0000000800" +
797            // IP header.
798            "45a00148000040003d115087d18194fb0a0f7af2" +
799            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
800            // NOTE: The destination port is a non-DHCP port.
801            "0043aaaa01341268" +
802            // BOOTP header.
803            "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
804            // MAC address.
805            "9cd91700000000000000000000000000" +
806            // Server name.
807            "0000000000000000000000000000000000000000000000000000000000000000" +
808            "0000000000000000000000000000000000000000000000000000000000000000" +
809            // File.
810            "0000000000000000000000000000000000000000000000000000000000000000" +
811            "0000000000000000000000000000000000000000000000000000000000000000" +
812            "0000000000000000000000000000000000000000000000000000000000000000" +
813            "0000000000000000000000000000000000000000000000000000000000000000" +
814            // Options.
815            "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
816            "d18180060f0777766d2e6564751c040a0fffffff000000"));
817
818        try {
819            DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
820            fail("Packet with invalid dst port did not throw ParseException");
821        } catch (ParseException expected) {}
822    }
823
824    @SmallTest
825    public void testMultipleRouters() throws Exception {
826        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
827            // Ethernet header.
828            "fc3d93000000" + "081735000000" + "0800" +
829            // IP header.
830            "45000148c2370000ff117ac2c0a8bd02ffffffff" +
831            // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
832            "0043004401343beb" +
833            // BOOTP header.
834            "0201060027f518e20000800000000000c0a8bd310000000000000000" +
835            // MAC address.
836            "fc3d9300000000000000000000000000" +
837            // Server name.
838            "0000000000000000000000000000000000000000000000000000000000000000" +
839            "0000000000000000000000000000000000000000000000000000000000000000" +
840            // File.
841            "0000000000000000000000000000000000000000000000000000000000000000" +
842            "0000000000000000000000000000000000000000000000000000000000000000" +
843            "0000000000000000000000000000000000000000000000000000000000000000" +
844            "0000000000000000000000000000000000000000000000000000000000000000" +
845            // Options.
846            "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
847            "0308c0a8bd01ffffff0006080808080808080404ff000000000000"));
848
849        DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
850        assertTrue(offerPacket instanceof DhcpOfferPacket);
851        assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
852        DhcpResults dhcpResults = offerPacket.toDhcpResults();
853        assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
854                null, "192.171.189.2", null, 28800, false, 0, dhcpResults);
855    }
856
857    @SmallTest
858    public void testDiscoverPacket() throws Exception {
859        short secs = 7;
860        int transactionId = 0xdeadbeef;
861        byte[] hwaddr = {
862                (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a
863        };
864
865        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
866                DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr,
867                false /* do unicast */, DhcpClient.REQUESTED_PARAMS);
868
869        byte[] headers = new byte[] {
870            // Ethernet header.
871            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
872            (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
873            (byte) 0x08, (byte) 0x00,
874            // IP header.
875            (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56,
876            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00,
877            (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88,
878            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
879            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
880            // UDP header.
881            (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
882            (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a,
883            // BOOTP.
884            (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00,
885            (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
886            (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00,
887            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
888            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
889            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
890            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
891            (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b,
892            (byte) 0xb1, (byte) 0x7a
893        };
894        byte[] options = new byte[] {
895            // Magic cookie 0x63825363.
896            (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
897            // Message type DISCOVER.
898            (byte) 0x35, (byte) 0x01, (byte) 0x01,
899            // Client identifier Ethernet, da:01:19:5b:b1:7a.
900            (byte) 0x3d, (byte) 0x07,
901                    (byte) 0x01,
902                    (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
903            // Max message size 1500.
904            (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc,
905            // Version "android-dhcp-???".
906            (byte) 0x3c, (byte) 0x10,
907                    'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?',
908            // Hostname "android-01234567890abcde"
909            (byte) 0x0c, (byte) 0x18,
910                    'a', 'n', 'd', 'r', 'o', 'i', 'd', '-',
911                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e',
912            // Requested parameter list.
913            (byte) 0x37, (byte) 0x0a,
914                DHCP_SUBNET_MASK,
915                DHCP_ROUTER,
916                DHCP_DNS_SERVER,
917                DHCP_DOMAIN_NAME,
918                DHCP_MTU,
919                DHCP_BROADCAST_ADDRESS,
920                DHCP_LEASE_TIME,
921                DHCP_RENEWAL_TIME,
922                DHCP_REBINDING_TIME,
923                DHCP_VENDOR_INFO,
924            // End options.
925            (byte) 0xff,
926            // Our packets are always of even length. TODO: find out why and possibly fix it.
927            (byte) 0x00
928        };
929        byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length];
930        assertTrue((expected.length & 1) == 0);
931        System.arraycopy(headers, 0, expected, 0, headers.length);
932        System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length);
933
934        byte[] actual = new byte[packet.limit()];
935        packet.get(actual);
936        String msg =
937                "Expected:\n  " + Arrays.toString(expected) +
938                "\nActual:\n  " + Arrays.toString(actual);
939        assertTrue(msg, Arrays.equals(expected, actual));
940    }
941}
942