1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.dhcp6;
18 
19 import static com.android.net.module.util.NetworkStackConstants.DHCP_MAX_OPTION_LEN;
20 
21 import android.net.MacAddress;
22 import android.util.Log;
23 
24 import androidx.annotation.NonNull;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.util.HexDump;
28 import com.android.net.module.util.Struct;
29 import com.android.net.module.util.structs.IaPdOption;
30 import com.android.net.module.util.structs.IaPrefixOption;
31 
32 import java.nio.BufferUnderflowException;
33 import java.nio.ByteBuffer;
34 import java.nio.ByteOrder;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Objects;
39 import java.util.OptionalInt;
40 
41 /**
42  * Defines basic data and operations needed to build and use packets for the
43  * DHCPv6 protocol. Subclasses create the specific packets used at each
44  * stage of the negotiation.
45  *
46  * @hide
47  */
48 public class Dhcp6Packet {
49     private static final String TAG = Dhcp6Packet.class.getSimpleName();
50     /**
51      * DHCPv6 Message Type.
52      */
53     public static final byte DHCP6_MESSAGE_TYPE_SOLICIT = 1;
54     public static final byte DHCP6_MESSAGE_TYPE_ADVERTISE = 2;
55     public static final byte DHCP6_MESSAGE_TYPE_REQUEST = 3;
56     public static final byte DHCP6_MESSAGE_TYPE_CONFIRM = 4;
57     public static final byte DHCP6_MESSAGE_TYPE_RENEW = 5;
58     public static final byte DHCP6_MESSAGE_TYPE_REBIND = 6;
59     public static final byte DHCP6_MESSAGE_TYPE_REPLY = 7;
60     public static final byte DHCP6_MESSAGE_TYPE_RELEASE = 8;
61     public static final byte DHCP6_MESSAGE_TYPE_DECLINE = 9;
62     public static final byte DHCP6_MESSAGE_TYPE_RECONFIGURE = 10;
63     public static final byte DHCP6_MESSAGE_TYPE_INFORMATION_REQUEST = 11;
64     public static final byte DHCP6_MESSAGE_TYPE_RELAY_FORW = 12;
65     public static final byte DHCP6_MESSAGE_TYPE_RELAY_REPL = 13;
66 
67     /**
68      * DHCPv6 Optional Type: Client Identifier.
69      * DHCPv6 message from client must have this option.
70      */
71     public static final byte DHCP6_CLIENT_IDENTIFIER = 1;
72     @NonNull
73     protected final byte[] mClientDuid;
74 
75     /**
76      * DHCPv6 Optional Type: Server Identifier.
77      */
78     public static final byte DHCP6_SERVER_IDENTIFIER = 2;
79     protected final byte[] mServerDuid;
80 
81     /**
82      * DHCPv6 Optional Type: Option Request Option.
83      */
84     public static final byte DHCP6_OPTION_REQUEST_OPTION = 6;
85 
86     /**
87      * DHCPv6 Optional Type: Elapsed time.
88      * This time is expressed in hundredths of a second.
89      */
90     public static final byte DHCP6_ELAPSED_TIME = 8;
91     protected final int mElapsedTime;
92 
93     /**
94      * DHCPv6 Optional Type: Status Code.
95      */
96     public static final byte DHCP6_STATUS_CODE = 13;
97     private static final byte MIN_STATUS_CODE_OPT_LEN = 6;
98     protected short mStatusCode;
99 
100     public static final short STATUS_SUCCESS           = 0;
101     public static final short STATUS_UNSPEC_FAIL       = 1;
102     public static final short STATUS_NO_ADDRS_AVAIL    = 2;
103     public static final short STATUS_NO_BINDING        = 3;
104     public static final short STATUS_NOT_ONLINK        = 4;
105     public static final short STATUS_USE_MULTICAST     = 5;
106     public static final short STATUS_NO_PREFIX_AVAIL   = 6;
107 
108     /**
109      * DHCPv6 zero-length Optional Type: Rapid Commit. Per RFC4039, both DHCPDISCOVER and DHCPACK
110      * packet may include this option.
111      */
112     public static final byte DHCP6_RAPID_COMMIT = 14;
113     public boolean mRapidCommit;
114 
115     /**
116      * DHCPv6 Optional Type: IA_PD.
117      */
118     public static final byte DHCP6_IA_PD = 25;
119     @NonNull
120     protected final byte[] mIaPd;
121     @NonNull
122     protected PrefixDelegation mPrefixDelegation;
123 
124     /**
125      * DHCPv6 Optional Type: IA Prefix Option.
126      */
127     public static final byte DHCP6_IAPREFIX = 26;
128 
129     /**
130      * DHCPv6 Optional Type: SOL_MAX_RT.
131      */
132     public static final byte DHCP6_SOL_MAX_RT = 82;
133     private OptionalInt mSolMaxRt;
134 
135     /**
136      * The transaction identifier used in this particular DHCPv6 negotiation
137      */
138     protected final int mTransId;
139 
140     /**
141      * The unique identifier for IA_NA, IA_TA, IA_PD used in this particular DHCPv6 negotiation
142      */
143     protected int mIaId;
144     // Per rfc8415#section-12, the IAID MUST be consistent across restarts.
145     // Since currently only one IAID is supported, a well-known value can be used (0).
146     public static final int IAID = 0;
147 
Dhcp6Packet(int transId, int elapsedTime, @NonNull final byte[] clientDuid, final byte[] serverDuid, @NonNull final byte[] iapd)148     Dhcp6Packet(int transId, int elapsedTime, @NonNull final byte[] clientDuid,
149             final byte[] serverDuid, @NonNull final byte[] iapd) {
150         mTransId = transId;
151         mElapsedTime = elapsedTime;
152         mClientDuid = clientDuid;
153         mServerDuid = serverDuid;
154         mIaPd = iapd;
155     }
156 
157     /**
158      * Returns the transaction ID.
159      */
getTransactionId()160     public int getTransactionId() {
161         return mTransId;
162     }
163 
164     /**
165      * Returns decoded IA_PD options associated with IA_ID.
166      */
167     @VisibleForTesting
getPrefixDelegation()168     public PrefixDelegation getPrefixDelegation() {
169         return mPrefixDelegation;
170     }
171 
172     /**
173      * Returns IA_ID associated to IA_PD.
174      */
getIaId()175     public int getIaId() {
176         return mIaId;
177     }
178 
179     /**
180      * Returns the client's DUID.
181      */
182     @NonNull
getClientDuid()183     public byte[] getClientDuid() {
184         return mClientDuid;
185     }
186 
187     /**
188      * Returns the server's DUID.
189      */
getServerDuid()190     public byte[] getServerDuid() {
191         return mServerDuid;
192     }
193 
194     /**
195      * Returns the SOL_MAX_RT option value in milliseconds.
196      */
getSolMaxRtMs()197     public OptionalInt getSolMaxRtMs() {
198         return mSolMaxRt;
199     }
200 
201     /**
202      * A class to take DHCPv6 IA_PD option allocated from server.
203      * https://www.rfc-editor.org/rfc/rfc8415.html#section-21.21
204      */
205     public static class PrefixDelegation {
206         public final int iaid;
207         public final int t1;
208         public final int t2;
209         @NonNull
210         public final List<IaPrefixOption> ipos;
211         public final short statusCode;
212 
213         @VisibleForTesting
PrefixDelegation(int iaid, int t1, int t2, @NonNull final List<IaPrefixOption> ipos, short statusCode)214         public PrefixDelegation(int iaid, int t1, int t2,
215                 @NonNull final List<IaPrefixOption> ipos, short statusCode) {
216             Objects.requireNonNull(ipos);
217             this.iaid = iaid;
218             this.t1 = t1;
219             this.t2 = t2;
220             this.ipos = ipos;
221             this.statusCode = statusCode;
222         }
223 
PrefixDelegation(int iaid, int t1, int t2, @NonNull final List<IaPrefixOption> ipos)224         public PrefixDelegation(int iaid, int t1, int t2,
225                 @NonNull final List<IaPrefixOption> ipos) {
226             this(iaid, t1, t2, ipos, STATUS_SUCCESS /* statusCode */);
227         }
228 
229         /**
230          * Check whether or not the IA_PD option in DHCPv6 message is valid.
231          *
232          * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short.
233          */
isValid()234         public boolean isValid() {
235             if (iaid != IAID) {
236                 Log.w(TAG, "IA_ID doesn't match, expected: " + IAID + ", actual: " + iaid);
237                 return false;
238             }
239             if (t1 < 0 || t2 < 0) {
240                 Log.e(TAG, "IA_PD option with invalid T1 " + t1 + " or T2 " + t2);
241                 return false;
242             }
243             // Generally, t1 must be smaller or equal to t2 (except when t2 is 0).
244             if (t2 != 0 && t1 > t2) {
245                 Log.e(TAG, "IA_PD option with T1 " + t1 + " greater than T2 " + t2);
246                 return false;
247             }
248             return true;
249         }
250 
251         /**
252          * Decode an IA_PD option from the byte buffer.
253          */
decode(@onNull final ByteBuffer buffer)254         public static PrefixDelegation decode(@NonNull final ByteBuffer buffer)
255                 throws ParseException {
256             try {
257                 final int iaid = buffer.getInt();
258                 final int t1 = buffer.getInt();
259                 final int t2 = buffer.getInt();
260                 final List<IaPrefixOption> ipos = new ArrayList<IaPrefixOption>();
261                 short statusCode = STATUS_SUCCESS;
262                 while (buffer.remaining() > 0) {
263                     final int original = buffer.position();
264                     final short optionType = buffer.getShort();
265                     final int optionLen = buffer.getShort() & 0xFFFF;
266                     switch (optionType) {
267                         case DHCP6_IAPREFIX:
268                             buffer.position(original);
269                             final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer);
270                             Log.d(TAG, "IA Prefix Option: " + ipo);
271                             ipos.add(ipo);
272                             break;
273                         case DHCP6_STATUS_CODE:
274                             statusCode = buffer.getShort();
275                             // Skip the status message if any.
276                             if (optionLen > 2) {
277                                 skipOption(buffer, optionLen - 2);
278                             }
279                             break;
280                         default:
281                             skipOption(buffer, optionLen);
282                     }
283                 }
284                 return new PrefixDelegation(iaid, t1, t2, ipos, statusCode);
285             } catch (BufferUnderflowException e) {
286                 throw new ParseException(e.getMessage());
287             }
288         }
289 
290         /**
291          * Build an IA_PD option from given specific parameters, including IA_PREFIX options.
292          */
build()293         public ByteBuffer build() {
294             return build(ipos);
295         }
296 
297         /**
298          * Build an IA_PD option from given specific parameters, including IA_PREFIX options.
299          *
300          * Per RFC8415 section 21.13 if the Status Code option does not appear in a message in
301          * which the option could appear, the status of the message is assumed to be Success. So
302          * only put the Status Code option in IA_PD when the status code is not Success.
303          */
build(@onNull final List<IaPrefixOption> input)304         public ByteBuffer build(@NonNull final List<IaPrefixOption> input) {
305             final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
306                     + Struct.getSize(IaPrefixOption.class) * input.size()
307                     + (statusCode != STATUS_SUCCESS ? MIN_STATUS_CODE_OPT_LEN : 0));
308             iapd.putInt(iaid);
309             iapd.putInt(t1);
310             iapd.putInt(t2);
311             for (IaPrefixOption ipo : input) {
312                 ipo.writeToByteBuffer(iapd);
313             }
314             if (statusCode != STATUS_SUCCESS) {
315                 iapd.putShort(DHCP6_STATUS_CODE);
316                 iapd.putShort((short) 2);
317                 iapd.putShort(statusCode);
318             }
319             iapd.flip();
320             return iapd;
321         }
322 
323         /**
324          * Return valid IA prefix options to be used and extended in the Reply message. It may
325          * return empty list if there isn't any valid IA prefix option in the Reply message.
326          *
327          * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short.
328          * and handle status code such as NoPrefixAvail.
329          */
getValidIaPrefixes()330         public List<IaPrefixOption> getValidIaPrefixes() {
331             final List<IaPrefixOption> validIpos = new ArrayList<IaPrefixOption>();
332             for (IaPrefixOption ipo : ipos) {
333                 if (!ipo.isValid()) continue;
334                 validIpos.add(ipo);
335             }
336             return validIpos;
337         }
338 
339         @Override
toString()340         public String toString() {
341             return String.format("Prefix Delegation, iaid: %s, t1: %s, t2: %s, status code: %s,"
342                     + " IA prefix options: %s", iaid, t1, t2, statusCodeToString(statusCode), ipos);
343         }
344 
345         /**
346          * Compare the preferred lifetime in the IA prefix optin list and return the minimum one.
347          */
getMinimalPreferredLifetime()348         public long getMinimalPreferredLifetime() {
349             long min = Long.MAX_VALUE;
350             for (IaPrefixOption ipo : ipos) {
351                 min = (ipo.preferred != 0 && min > ipo.preferred) ? ipo.preferred : min;
352             }
353             return min;
354         }
355 
356         /**
357          * Compare the valid lifetime in the IA prefix optin list and return the minimum one.
358          */
getMinimalValidLifetime()359         public long getMinimalValidLifetime() {
360             long min = Long.MAX_VALUE;
361             for (IaPrefixOption ipo : ipos) {
362                 min = (ipo.valid != 0 && min > ipo.valid) ? ipo.valid : min;
363             }
364             return min;
365         }
366 
367         /**
368          * Return IA prefix option list to be renewed/rebound.
369          *
370          * Per RFC8415#section-18.2.4, client must not include any prefixes that it didn't obtain
371          * from server or that are no longer valid (that have a valid lifetime of 0). Section-18.3.4
372          * also mentions that server can inform client that it will not extend the prefix by setting
373          * T1 and T2 to values equal to the valid lifetime, so in this case client has no point in
374          * renewing as well.
375          */
getRenewableIaPrefixes()376         public List<IaPrefixOption> getRenewableIaPrefixes() {
377             final List<IaPrefixOption> toBeRenewed = getValidIaPrefixes();
378             toBeRenewed.removeIf(ipo -> ipo.preferred == 0 && ipo.valid == 0);
379             toBeRenewed.removeIf(ipo -> t1 == ipo.valid && t2 == ipo.valid);
380             return toBeRenewed;
381         }
382     }
383 
384     /**
385      * DHCPv6 packet parsing exception.
386      */
387     public static class ParseException extends Exception {
ParseException(String msg)388         ParseException(String msg) {
389             super(msg);
390         }
391     }
392 
statusCodeToString(short statusCode)393     private static String statusCodeToString(short statusCode) {
394         switch (statusCode) {
395             case STATUS_SUCCESS:
396                 return "Success";
397             case STATUS_UNSPEC_FAIL:
398                 return "UnspecFail";
399             case STATUS_NO_ADDRS_AVAIL:
400                 return "NoAddrsAvail";
401             case STATUS_NO_BINDING:
402                 return "NoBinding";
403             case STATUS_NOT_ONLINK:
404                 return "NotOnLink";
405             case STATUS_USE_MULTICAST:
406                 return "UseMulticast";
407             case STATUS_NO_PREFIX_AVAIL:
408                 return "NoPrefixAvail";
409             default:
410                 return "Unknown";
411         }
412     }
413 
skipOption(@onNull final ByteBuffer packet, int optionLen)414     private static void skipOption(@NonNull final ByteBuffer packet, int optionLen)
415             throws BufferUnderflowException {
416         for (int i = 0; i < optionLen; i++) {
417             packet.get();
418         }
419     }
420 
421     /**
422      * Creates a concrete Dhcp6Packet from the supplied ByteBuffer.
423      *
424      * The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the
425      * optional parameters are parsed and are stored in object fields. Client/Server message
426      * format:
427      *
428      *  0                   1                   2                   3
429      *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
430      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
431      * |    msg-type   |               transaction-id                  |
432      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
433      * |                                                               |
434      * .                            options                            .
435      * .                 (variable number and length)                  .
436      * |                                                               |
437      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
438      */
decode(@onNull final ByteBuffer packet)439     private static Dhcp6Packet decode(@NonNull final ByteBuffer packet) throws ParseException {
440         int elapsedTime = 0;
441         byte[] iapd = null;
442         byte[] serverDuid = null;
443         byte[] clientDuid = null;
444         short statusCode = STATUS_SUCCESS;
445         boolean rapidCommit = false;
446         int solMaxRt = 0;
447         PrefixDelegation pd = null;
448 
449         packet.order(ByteOrder.BIG_ENDIAN);
450 
451         // DHCPv6 message contents.
452         final int msgTypeAndTransId = packet.getInt();
453         final byte messageType = (byte) (msgTypeAndTransId >> 24);
454         final int transId = msgTypeAndTransId & 0xffffff;
455 
456         /**
457          * Parse DHCPv6 options, option format:
458          *
459          * 0                   1                   2                   3
460          * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
461          * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
462          * |          option-code          |           option-len          |
463          * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
464          * |                          option-data                          |
465          * |                      (option-len octets)                      |
466          * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
467          */
468         while (packet.hasRemaining()) {
469             try {
470                 final short optionType = packet.getShort();
471                 final int optionLen = packet.getShort() & 0xFFFF;
472                 int expectedLen = 0;
473 
474                 switch(optionType) {
475                     case DHCP6_SERVER_IDENTIFIER:
476                         expectedLen = optionLen;
477                         final byte[] sduid = new byte[expectedLen];
478                         packet.get(sduid, 0 /* offset */, expectedLen);
479                         serverDuid = sduid;
480                         break;
481                     case DHCP6_CLIENT_IDENTIFIER:
482                         expectedLen = optionLen;
483                         final byte[] cduid = new byte[expectedLen];
484                         packet.get(cduid, 0 /* offset */, expectedLen);
485                         clientDuid = cduid;
486                         break;
487                     case DHCP6_IA_PD:
488                         expectedLen = optionLen;
489                         final byte[] bytes = new byte[expectedLen];
490                         packet.get(bytes, 0 /* offset */, expectedLen);
491                         iapd = bytes;
492                         pd = PrefixDelegation.decode(ByteBuffer.wrap(iapd));
493                         break;
494                     case DHCP6_RAPID_COMMIT:
495                         expectedLen = 0;
496                         rapidCommit = true;
497                         break;
498                     case DHCP6_ELAPSED_TIME:
499                         expectedLen = 2;
500                         elapsedTime = (int) (packet.getShort() & 0xFFFF);
501                         break;
502                     case DHCP6_STATUS_CODE:
503                         expectedLen = optionLen;
504                         statusCode = packet.getShort();
505                         // Skip the status message (if any), which is a UTF-8 encoded text string
506                         // suitable for display to the end user, but is not useful for Dhcp6Client
507                         // to decide how to properly handle the status code.
508                         if (optionLen - 2 > 0) {
509                             skipOption(packet, optionLen - 2);
510                         }
511                         break;
512                     case DHCP6_SOL_MAX_RT:
513                         expectedLen = 4;
514                         solMaxRt = packet.getInt();
515                         break;
516                     default:
517                         expectedLen = optionLen;
518                         // BufferUnderflowException will be thrown if option is truncated.
519                         skipOption(packet, optionLen);
520                         break;
521                 }
522                 if (expectedLen != optionLen) {
523                     throw new ParseException(
524                             "Invalid length " + optionLen + " for option " + optionType
525                                     + ", expected " + expectedLen);
526                 }
527             } catch (BufferUnderflowException e) {
528                 throw new ParseException(e.getMessage());
529             }
530         }
531 
532         Dhcp6Packet newPacket;
533 
534         switch(messageType) {
535             case DHCP6_MESSAGE_TYPE_SOLICIT:
536                 newPacket = new Dhcp6SolicitPacket(transId, elapsedTime, clientDuid, iapd,
537                         rapidCommit);
538                 break;
539             case DHCP6_MESSAGE_TYPE_ADVERTISE:
540                 newPacket = new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
541                 break;
542             case DHCP6_MESSAGE_TYPE_REQUEST:
543                 newPacket = new Dhcp6RequestPacket(transId, elapsedTime, clientDuid, serverDuid,
544                         iapd);
545                 break;
546             case DHCP6_MESSAGE_TYPE_REPLY:
547                 newPacket = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd,
548                         rapidCommit);
549                 break;
550             case DHCP6_MESSAGE_TYPE_RENEW:
551                 newPacket = new Dhcp6RenewPacket(transId, elapsedTime, clientDuid, serverDuid,
552                         iapd);
553                 break;
554             case DHCP6_MESSAGE_TYPE_REBIND:
555                 newPacket = new Dhcp6RebindPacket(transId, elapsedTime, clientDuid, iapd);
556                 break;
557             default:
558                 throw new ParseException("Unimplemented DHCP6 message type %d" + messageType);
559         }
560 
561         if (pd != null) {
562             newPacket.mPrefixDelegation = pd;
563             newPacket.mIaId = pd.iaid;
564         }
565         newPacket.mStatusCode = statusCode;
566         newPacket.mRapidCommit = rapidCommit;
567         newPacket.mSolMaxRt =
568                 (solMaxRt >= 60 && solMaxRt <= 86400)
569                         ? OptionalInt.of(solMaxRt * 1000)
570                         : OptionalInt.empty();
571 
572         return newPacket;
573     }
574 
575     /**
576      * Parse a packet from an array of bytes, stopping at the given length.
577      */
decode(@onNull final byte[] packet, int length)578     public static Dhcp6Packet decode(@NonNull final byte[] packet, int length)
579             throws ParseException {
580         final ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
581         return decode(buffer);
582     }
583 
584     /**
585      * Follow RFC8415 section 18.2.9 and 18.2.10 to check if the received DHCPv6 message is valid.
586      */
isValid(int transId, @NonNull final byte[] clientDuid)587     public boolean isValid(int transId, @NonNull final byte[] clientDuid) {
588         if (mClientDuid == null) {
589             Log.e(TAG, "DHCPv6 message without Client DUID option");
590             return false;
591         }
592         if (!Arrays.equals(mClientDuid, clientDuid)) {
593             Log.e(TAG, "Unexpected client DUID " + HexDump.toHexString(mClientDuid)
594                     + ", expected " + HexDump.toHexString(clientDuid));
595             return false;
596         }
597         if (mTransId != transId) {
598             Log.e(TAG, "Unexpected transaction ID " + mTransId + ", expected " + transId);
599             return false;
600         }
601         if (mPrefixDelegation == null) {
602             Log.e(TAG, "DHCPv6 message without IA_PD option, ignoring");
603             return false;
604         }
605         if (!mPrefixDelegation.isValid()) {
606             Log.e(TAG, "DHCPv6 message takes invalid IA_PD option, ignoring");
607             return false;
608         }
609         //TODO: check if the status code is success or not.
610         return true;
611     }
612 
613     /**
614      * Returns the client DUID, follows RFC 8415 and creates a client DUID
615      * based on the link-layer address(DUID-LL).
616      *
617      * TODO: use Struct to build and parse DUID.
618      *
619      * 0                   1                   2                   3
620      * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
621      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
622      * |         DUID-Type (3)         |    hardware type (16 bits)    |
623      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
624      * .                                                               .
625      * .             link-layer address (variable length)              .
626      * .                                                               .
627      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
628      */
createClientDuid(@onNull final MacAddress macAddress)629     public static byte[] createClientDuid(@NonNull final MacAddress macAddress) {
630         final byte[] duid = new byte[10];
631         // type: Link-layer address(3)
632         duid[0] = (byte) 0x00;
633         duid[1] = (byte) 0x03;
634         // hardware type: Ethernet(1)
635         duid[2] = (byte) 0x00;
636         duid[3] = (byte) 0x01;
637         System.arraycopy(macAddress.toByteArray() /* src */, 0 /* srcPos */, duid /* dest */,
638                 4 /* destPos */, 6 /* length */);
639         return duid;
640     }
641 
642     /**
643      * Adds an optional parameter containing an array of bytes.
644      */
addTlv(ByteBuffer buf, short type, @NonNull byte[] payload)645     protected static void addTlv(ByteBuffer buf, short type, @NonNull byte[] payload) {
646         if (payload.length > DHCP_MAX_OPTION_LEN) {
647             throw new IllegalArgumentException("DHCP option too long: "
648                     + payload.length + " vs. " + DHCP_MAX_OPTION_LEN);
649         }
650         buf.putShort(type);
651         buf.putShort((short) payload.length);
652         buf.put(payload);
653     }
654 
655     /**
656      * Adds an optional parameter containing a short integer.
657      */
addTlv(ByteBuffer buf, short type, short value)658     protected static void addTlv(ByteBuffer buf, short type, short value) {
659         buf.putShort(type);
660         buf.putShort((short) 2);
661         buf.putShort(value);
662     }
663 
664     /**
665      * Adds an optional parameter containing zero-length value.
666      */
addTlv(ByteBuffer buf, short type)667     protected static void addTlv(ByteBuffer buf, short type) {
668         buf.putShort(type);
669         buf.putShort((short) 0);
670     }
671 
672     /**
673      * Builds a DHCPv6 SOLICIT packet from the required specified parameters.
674      */
buildSolicitPacket(int transId, long millisecs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, boolean rapidCommit)675     public static ByteBuffer buildSolicitPacket(int transId, long millisecs,
676             @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, boolean rapidCommit) {
677         final Dhcp6SolicitPacket pkt =
678                 new Dhcp6SolicitPacket(transId, (int) (millisecs / 10) /* elapsed time */,
679                         clientDuid, iapd, rapidCommit);
680         return pkt.buildPacket();
681     }
682 
683     /**
684      * Builds a DHCPv6 ADVERTISE packet from the required specified parameters.
685      */
buildAdvertisePacket(int transId, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)686     public static ByteBuffer buildAdvertisePacket(int transId, @NonNull final byte[] iapd,
687             @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
688         final Dhcp6AdvertisePacket pkt =
689                 new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
690         return pkt.buildPacket();
691     }
692 
693     /**
694      * Builds a DHCPv6 REPLY packet from the required specified parameters.
695      */
buildReplyPacket(int transId, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid, boolean rapidCommit)696     public static ByteBuffer buildReplyPacket(int transId, @NonNull final byte[] iapd,
697             @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid,
698             boolean rapidCommit) {
699         final Dhcp6ReplyPacket pkt =
700                 new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd, rapidCommit);
701         return pkt.buildPacket();
702     }
703 
704     /**
705      * Builds a DHCPv6 REQUEST packet from the required specified parameters.
706      */
buildRequestPacket(int transId, long millisecs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)707     public static ByteBuffer buildRequestPacket(int transId, long millisecs,
708             @NonNull final byte[] iapd, @NonNull final byte[] clientDuid,
709             @NonNull final byte[] serverDuid) {
710         final Dhcp6RequestPacket pkt =
711                 new Dhcp6RequestPacket(transId, (int) (millisecs / 10) /* elapsed time */,
712                         clientDuid, serverDuid, iapd);
713         return pkt.buildPacket();
714     }
715 
716     /**
717      * Builds a DHCPv6 RENEW packet from the required specified parameters.
718      */
buildRenewPacket(int transId, long millisecs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)719     public static ByteBuffer buildRenewPacket(int transId, long millisecs,
720             @NonNull final byte[] iapd, @NonNull final byte[] clientDuid,
721             @NonNull final byte[] serverDuid) {
722         final Dhcp6RenewPacket pkt =
723                 new Dhcp6RenewPacket(transId, (int) (millisecs / 10) /* elapsed time */, clientDuid,
724                         serverDuid, iapd);
725         return pkt.buildPacket();
726     }
727 
728     /**
729      * Builds a DHCPv6 REBIND packet from the required specified parameters.
730      */
buildRebindPacket(int transId, long millisecs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid)731     public static ByteBuffer buildRebindPacket(int transId, long millisecs,
732             @NonNull final byte[] iapd, @NonNull final byte[] clientDuid) {
733         final Dhcp6RebindPacket pkt = new Dhcp6RebindPacket(transId,
734                 (int) (millisecs / 10) /* elapsed time */, clientDuid, iapd);
735         return pkt.buildPacket();
736     }
737 }
738