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