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