• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.annotation.NonNull;
22 import androidx.annotation.VisibleForTesting;
23 
24 import com.android.net.module.util.Struct;
25 import com.android.net.module.util.structs.IaPrefixOption;
26 
27 import java.nio.BufferUnderflowException;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.nio.charset.StandardCharsets;
31 
32 /**
33  * Defines basic data and operations needed to build and use packets for the
34  * DHCPv6 protocol. Subclasses create the specific packets used at each
35  * stage of the negotiation.
36  *
37  * @hide
38  */
39 public class Dhcp6Packet {
40 
41     /**
42      * DHCPv6 Message Type.
43      */
44     public static final byte DHCP6_MESSAGE_TYPE_SOLICIT = 1;
45     public static final byte DHCP6_MESSAGE_TYPE_ADVERTISE = 2;
46     public static final byte DHCP6_MESSAGE_TYPE_REQUEST = 3;
47     public static final byte DHCP6_MESSAGE_TYPE_CONFIRM = 4;
48     public static final byte DHCP6_MESSAGE_TYPE_RENEW = 5;
49     public static final byte DHCP6_MESSAGE_TYPE_REBIND = 6;
50     public static final byte DHCP6_MESSAGE_TYPE_REPLY = 7;
51     public static final byte DHCP6_MESSAGE_TYPE_RELEASE = 8;
52     public static final byte DHCP6_MESSAGE_TYPE_DECLINE = 9;
53     public static final byte DHCP6_MESSAGE_TYPE_RECONFIGURE = 10;
54     public static final byte DHCP6_MESSAGE_TYPE_INFORMATION_REQUEST = 11;
55     public static final byte DHCP6_MESSAGE_TYPE_RELAY_FORW = 12;
56     public static final byte DHCP6_MESSAGE_TYPE_RELAY_REPL = 13;
57 
58     /**
59      * DHCPv6 Optional Type: Client Identifier.
60      * DHCPv6 message from client must have this option.
61      */
62     public static final byte DHCP6_CLIENT_IDENTIFIER = 1;
63     @NonNull
64     protected final byte[] mClientDuid;
65 
66     /**
67      * DHCPv6 Optional Type: Server Identifier.
68      */
69     public static final byte DHCP6_SERVER_IDENTIFIER = 2;
70     protected final byte[] mServerDuid;
71 
72     /**
73      * DHCPv6 Optional Type: Elapsed time.
74      */
75     public static final byte DHCP6_ELAPSED_TIME = 8;
76     protected final short mSecs;
77 
78     /**
79      * DHCPv6 Optional Type: Status Code.
80      */
81     public static final byte DHCP6_STATUS_CODE = 13;
82     protected short mStatusCode;
83     protected String mStatusMsg;
84 
85     public static final short STATUS_SUCCESS           = 0;
86     public static final short STATUS_UNSPEC_FAIL       = 1;
87     public static final short STATUS_NO_ADDR_AVAI      = 2;
88     public static final short STATUS_NO_BINDING        = 3;
89     public static final short STATUS_PREFIX_NOT_ONLINK = 4;
90     public static final short STATUS_USE_MULTICAST     = 5;
91     public static final short STATUS_NO_PREFIX_AVAI    = 6;
92 
93     /**
94      * DHCPv6 Optional Type: IA_PD.
95      */
96     public static final byte DHCP6_IA_PD = 25;
97     @NonNull
98     protected final byte[] mIaPd;
99     @NonNull
100     protected PrefixDelegation mPrefixDelegation;
101 
102     /**
103      * The transaction identifier used in this particular DHCPv6 negotiation
104      */
105     protected final int mTransId;
106 
107     /**
108      * The unique identifier for IA_NA, IA_TA, IA_PD used in this particular DHCPv6 negotiation
109      */
110     protected int mIaId;
111 
Dhcp6Packet(int transId, short secs, @NonNull final byte[] clientDuid, final byte[] serverDuid, @NonNull final byte[] iapd)112     Dhcp6Packet(int transId, short secs, @NonNull final byte[] clientDuid, final byte[] serverDuid,
113             @NonNull final byte[] iapd) {
114         mTransId = transId;
115         mSecs = secs;
116         mClientDuid = clientDuid;
117         mServerDuid = serverDuid;
118         mIaPd = iapd;
119     }
120 
121     /**
122      * Returns the transaction ID.
123      */
getTransactionId()124     public int getTransactionId() {
125         return mTransId;
126     }
127 
128     /**
129      * Returns IA_ID associated to IA_PD.
130      */
getIaId()131     public int getIaId() {
132         return mIaId;
133     }
134 
135     /**
136      * Returns the client's DUID.
137      */
138     @NonNull
getClientDuid()139     public byte[] getClientDuid() {
140         return mClientDuid;
141     }
142 
143     /**
144      * Returns the server's DUID.
145      */
getServerDuid()146     public byte[] getServerDuid() {
147         return mServerDuid;
148     }
149 
150     /**
151      * A class to take DHCPv6 IA_PD option allocated from server.
152      * https://www.rfc-editor.org/rfc/rfc8415.html#section-21.21
153      */
154     public static class PrefixDelegation {
155         public int iaid;
156         public int t1;
157         public int t2;
158         public final IaPrefixOption ipo;
159 
PrefixDelegation(int iaid, int t1, int t2, final IaPrefixOption ipo)160         PrefixDelegation(int iaid, int t1, int t2, final IaPrefixOption ipo) {
161             this.iaid = iaid;
162             this.t1 = t1;
163             this.t2 = t2;
164             this.ipo = ipo;
165         }
166 
167         @Override
toString()168         public String toString() {
169             return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2
170                     + ", prefix " + ipo;
171         }
172     }
173 
174     /**
175      * DHCPv6 packet parsing exception.
176      */
177     public static class ParseException extends Exception {
ParseException(String msg)178         ParseException(String msg) {
179             super(msg);
180         }
181     }
182 
skipOption(@onNull final ByteBuffer packet, int optionLen)183     private static void skipOption(@NonNull final ByteBuffer packet, int optionLen)
184             throws BufferUnderflowException {
185         for (int i = 0; i < optionLen; i++) {
186             packet.get();
187         }
188     }
189 
190     /**
191      * Reads a string of specified length from the buffer.
192      *
193      * TODO: move to a common place which can be shared with DhcpClient.
194      */
readAsciiString(@onNull final ByteBuffer buf, int byteCount, boolean isNullOk)195     private static String readAsciiString(@NonNull final ByteBuffer buf, int byteCount,
196             boolean isNullOk) {
197         final byte[] bytes = new byte[byteCount];
198         buf.get(bytes);
199         return readAsciiString(bytes, isNullOk);
200     }
201 
readAsciiString(@onNull final byte[] payload, boolean isNullOk)202     private static String readAsciiString(@NonNull final byte[] payload, boolean isNullOk) {
203         final byte[] bytes = payload;
204         int length = bytes.length;
205         if (!isNullOk) {
206             // Stop at the first null byte. This is because some DHCP options (e.g., the domain
207             // name) are passed to netd via FrameworkListener, which refuses arguments containing
208             // null bytes. We don't do this by default because vendorInfo is an opaque string which
209             // could in theory contain null bytes.
210             for (length = 0; length < bytes.length; length++) {
211                 if (bytes[length] == 0) {
212                     break;
213                 }
214             }
215         }
216         return new String(bytes, 0, length, StandardCharsets.US_ASCII);
217     }
218 
219     /**
220      * Creates a concrete Dhcp6Packet from the supplied ByteBuffer.
221      *
222      * The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the
223      * optional parameters are parsed and are stored in object fields. Client/Server message
224      * format:
225      *
226      *  0                   1                   2                   3
227      *  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
228      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
229      * |    msg-type   |               transaction-id                  |
230      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
231      * |                                                               |
232      * .                            options                            .
233      * .                 (variable number and length)                  .
234      * |                                                               |
235      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
236      */
237     @VisibleForTesting
decodePacket(@onNull final ByteBuffer packet)238     static Dhcp6Packet decodePacket(@NonNull final ByteBuffer packet) throws ParseException {
239         short secs = 0;
240         byte[] iapd = null;
241         byte[] serverDuid = null;
242         byte[] clientDuid = null;
243         short statusCode = STATUS_SUCCESS;
244         String statusMsg = null;
245 
246         packet.order(ByteOrder.BIG_ENDIAN);
247 
248         // DHCPv6 message contents.
249         final int msgTypeAndTransId = packet.getInt();
250         final byte messageType = (byte) (msgTypeAndTransId >> 24);
251         final int transId = msgTypeAndTransId & 0x0FFF;
252 
253         /**
254          * Parse DHCPv6 options, option format:
255          *
256          * 0                   1                   2                   3
257          * 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
258          * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
259          * |          option-code          |           option-len          |
260          * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
261          * |                          option-data                          |
262          * |                      (option-len octets)                      |
263          * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
264          */
265         while (packet.hasRemaining()) {
266             try {
267                 final short optionType = packet.getShort();
268                 final int optionLen = packet.getShort() & 0xFFFF;
269                 int expectedLen = 0;
270 
271                 switch(optionType) {
272                     case DHCP6_SERVER_IDENTIFIER:
273                         expectedLen = optionLen;
274                         final byte[] sduid = new byte[expectedLen];
275                         packet.get(sduid, 0 /* offset */, expectedLen);
276                         serverDuid = sduid;
277                         break;
278                     case DHCP6_CLIENT_IDENTIFIER:
279                         expectedLen = optionLen;
280                         final byte[] cduid = new byte[expectedLen];
281                         packet.get(cduid, 0 /* offset */, expectedLen);
282                         clientDuid = cduid;
283                         break;
284                     case DHCP6_IA_PD:
285                         expectedLen = optionLen;
286                         final byte[] bytes = new byte[expectedLen];
287                         packet.get(bytes, 0 /* offset */, expectedLen);
288                         iapd = bytes;
289                         break;
290                     case DHCP6_ELAPSED_TIME:
291                         expectedLen = 2;
292                         secs = packet.getShort();
293                         break;
294                     case DHCP6_STATUS_CODE:
295                         expectedLen = optionLen;
296                         statusCode = packet.getShort();
297                         statusMsg = readAsciiString(packet, expectedLen - 2, false /* isNullOk */);
298                         break;
299                     default:
300                         expectedLen = optionLen;
301                         // BufferUnderflowException will be thrown if option is truncated.
302                         skipOption(packet, optionLen);
303                         break;
304                 }
305                 if (expectedLen != optionLen) {
306                     throw new ParseException(
307                             "Invalid length " + optionLen + " for option " + optionType
308                                     + ", expected " + expectedLen);
309                 }
310             } catch (BufferUnderflowException e) {
311                 throw new ParseException(e.getMessage());
312             }
313         }
314 
315         Dhcp6Packet newPacket;
316 
317         switch(messageType) {
318             case DHCP6_MESSAGE_TYPE_SOLICIT:
319                 newPacket = new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd);
320                 break;
321             case DHCP6_MESSAGE_TYPE_ADVERTISE:
322                 newPacket = new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
323                 break;
324             case DHCP6_MESSAGE_TYPE_REQUEST:
325                 newPacket = new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd);
326                 break;
327             case DHCP6_MESSAGE_TYPE_REPLY:
328                 newPacket = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd);
329                 break;
330             case DHCP6_MESSAGE_TYPE_RENEW:
331                 newPacket = new Dhcp6RenewPacket(transId, secs, clientDuid, serverDuid, iapd);
332                 break;
333             case DHCP6_MESSAGE_TYPE_REBIND:
334                 newPacket = new Dhcp6RebindPacket(transId, secs, clientDuid, iapd);
335                 break;
336             default:
337                 throw new ParseException("Unimplemented DHCP6 message type %d" + messageType);
338         }
339 
340         if (iapd != null) {
341             final ByteBuffer buffer = ByteBuffer.wrap(iapd);
342             final int iaid = buffer.getInt();
343             final int t1 = buffer.getInt();
344             final int t2 = buffer.getInt();
345             final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer);
346             newPacket.mPrefixDelegation = new PrefixDelegation(iaid, t1, t2, ipo);
347             newPacket.mIaId = iaid;
348         }
349         newPacket.mStatusCode = statusCode;
350         newPacket.mStatusMsg = statusMsg;
351 
352         return newPacket;
353     }
354 
355     /**
356      * Parse a packet from an array of bytes, stopping at the given length.
357      */
decodePacket(@onNull final byte[] packet, int length)358     public static Dhcp6Packet decodePacket(@NonNull final byte[] packet, int length)
359             throws ParseException {
360         final ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
361         return decodePacket(buffer);
362     }
363 
364     /**
365      * Adds an optional parameter containing an array of bytes.
366      */
addTlv(ByteBuffer buf, short type, @NonNull byte[] payload)367     protected static void addTlv(ByteBuffer buf, short type, @NonNull byte[] payload) {
368         if (payload.length > DHCP_MAX_OPTION_LEN) {
369             throw new IllegalArgumentException("DHCP option too long: "
370                     + payload.length + " vs. " + DHCP_MAX_OPTION_LEN);
371         }
372         buf.putShort(type);
373         buf.putShort((short) payload.length);
374         buf.put(payload);
375     }
376 
377     /**
378      * Adds an optional parameter containing a short integer.
379      */
addTlv(ByteBuffer buf, short type, short value)380     protected static void addTlv(ByteBuffer buf, short type, short value) {
381         buf.putShort(type);
382         buf.putShort((short) 2);
383         buf.putShort(value);
384     }
385 
386     /**
387      * Builds a DHCPv6 SOLICIT packet from the required specified parameters.
388      */
buildSolicitPacket(int transId, short secs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid)389     public static ByteBuffer buildSolicitPacket(int transId, short secs, @NonNull final byte[] iapd,
390             @NonNull final byte[] clientDuid) {
391         final Dhcp6SolicitPacket pkt = new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd);
392         return pkt.buildPacket();
393     }
394 
395     /**
396      * Builds a DHCPv6 ADVERTISE packet from the required specified parameters.
397      */
buildAdvertisePacket(int transId, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)398     public static ByteBuffer buildAdvertisePacket(int transId, @NonNull final byte[] iapd,
399             @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
400         final Dhcp6AdvertisePacket pkt =
401                 new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
402         return pkt.buildPacket();
403     }
404 
405     /**
406      * Builds a DHCPv6 REPLY packet from the required specified parameters.
407      */
buildReplyPacket(int transId, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)408     public static ByteBuffer buildReplyPacket(int transId, @NonNull final byte[] iapd,
409             @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
410         final Dhcp6ReplyPacket pkt = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd);
411         return pkt.buildPacket();
412     }
413 
414     /**
415      * Builds a DHCPv6 REQUEST packet from the required specified parameters.
416      */
buildRequestPacket(int transId, short secs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)417     public static ByteBuffer buildRequestPacket(int transId, short secs, @NonNull final byte[] iapd,
418             @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
419         final Dhcp6RequestPacket pkt =
420                 new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd);
421         return pkt.buildPacket();
422     }
423 
424     /**
425      * Builds a DHCPv6 RENEW packet from the required specified parameters.
426      */
buildRenewPacket(int transId, short secs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid)427     public static ByteBuffer buildRenewPacket(int transId, short secs, @NonNull final byte[] iapd,
428             @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
429         final Dhcp6RenewPacket pkt =
430                 new Dhcp6RenewPacket(transId, secs, clientDuid, serverDuid, iapd);
431         return pkt.buildPacket();
432     }
433 
434     /**
435      * Builds a DHCPv6 REBIND packet from the required specified parameters.
436      */
buildRebindPacket(int transId, short secs, @NonNull final byte[] iapd, @NonNull final byte[] clientDuid)437     public static ByteBuffer buildRebindPacket(int transId, short secs, @NonNull final byte[] iapd,
438             @NonNull final byte[] clientDuid) {
439         final Dhcp6RebindPacket pkt = new Dhcp6RebindPacket(transId, secs, clientDuid, iapd);
440         return pkt.buildPacket();
441     }
442 }
443