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 android.net.dhcp6.Dhcp6Packet.IAID; 20 import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; 21 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; 22 import static android.system.OsConstants.AF_INET6; 23 import static android.system.OsConstants.IPPROTO_UDP; 24 import static android.system.OsConstants.SOCK_DGRAM; 25 import static android.system.OsConstants.SOCK_NONBLOCK; 26 27 import static com.android.net.module.util.NetworkStackConstants.ALL_DHCP_RELAY_AGENTS_AND_SERVERS; 28 import static com.android.net.module.util.NetworkStackConstants.DHCP6_CLIENT_PORT; 29 import static com.android.net.module.util.NetworkStackConstants.DHCP6_SERVER_PORT; 30 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; 31 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; 32 33 import android.content.Context; 34 import android.net.ip.IpClient; 35 import android.net.util.SocketUtils; 36 import android.os.Handler; 37 import android.os.Message; 38 import android.os.SystemClock; 39 import android.system.ErrnoException; 40 import android.system.Os; 41 import android.util.Log; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 46 import com.android.internal.util.HexDump; 47 import com.android.internal.util.State; 48 import com.android.internal.util.StateMachine; 49 import com.android.internal.util.WakeupMessage; 50 import com.android.net.module.util.DeviceConfigUtils; 51 import com.android.net.module.util.InterfaceParams; 52 import com.android.net.module.util.PacketReader; 53 import com.android.net.module.util.structs.IaPrefixOption; 54 55 import java.io.FileDescriptor; 56 import java.io.IOException; 57 import java.net.SocketException; 58 import java.nio.ByteBuffer; 59 import java.util.Collections; 60 import java.util.List; 61 import java.util.Random; 62 import java.util.function.IntSupplier; 63 64 /** 65 * A DHCPv6 client. 66 * 67 * So far only support IA_PD (prefix delegation), not for IA_NA/IA_TA yet. 68 * 69 * @hide 70 */ 71 public class Dhcp6Client extends StateMachine { 72 private static final String TAG = Dhcp6Client.class.getSimpleName(); 73 private static final boolean DBG = true; 74 75 // Dhcp6Client shares the same handler with IpClient, define the base command range for 76 // both public and private messages used in Dhcp6Client, to avoid commands overlap. 77 // Public messages. 78 private static final int PUBLIC_BASE = IpClient.DHCP6CLIENT_CMD_BASE; 79 // Commands from controller to start/stop DHCPv6 80 public static final int CMD_START_DHCP6 = PUBLIC_BASE + 1; 81 public static final int CMD_STOP_DHCP6 = PUBLIC_BASE + 2; 82 // Notification from DHCPv6 state machine post DHCPv6 discovery/renewal. Indicates 83 // success/failure 84 public static final int CMD_DHCP6_RESULT = PUBLIC_BASE + 3; 85 // Message.arg1 arguments to CMD_DHCP6_RESULT notification 86 public static final int DHCP6_PD_SUCCESS = 1; 87 public static final int DHCP6_PD_PREFIX_EXPIRED = 2; 88 89 // Notification from DHCPv6 state machine before quitting 90 public static final int CMD_ON_QUIT = PUBLIC_BASE + 4; 91 92 // Internal messages. 93 private static final int PRIVATE_BASE = IpClient.DHCP6CLIENT_CMD_BASE + 100; 94 private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 1; 95 private static final int CMD_KICK = PRIVATE_BASE + 2; 96 private static final int CMD_DHCP6_PD_RENEW = PRIVATE_BASE + 3; 97 private static final int CMD_DHCP6_PD_REBIND = PRIVATE_BASE + 4; 98 private static final int CMD_DHCP6_PD_EXPIRE = PRIVATE_BASE + 5; 99 100 // Transmission and Retransmission parameters in milliseconds. 101 private static final int SECONDS = 1000; 102 private static final int SOL_TIMEOUT = 1 * SECONDS; 103 private static final int SOL_MAX_RT = 3600 * SECONDS; 104 private static final int REQ_TIMEOUT = 1 * SECONDS; 105 private static final int REQ_MAX_RT = 30 * SECONDS; 106 private static final int REQ_MAX_RC = 10; 107 private static final int REN_TIMEOUT = 10 * SECONDS; 108 private static final int REN_MAX_RT = 600 * SECONDS; 109 private static final int REB_TIMEOUT = 10 * SECONDS; 110 private static final int REB_MAX_RT = 600 * SECONDS; 111 112 private int mSolMaxRtMs = SOL_MAX_RT; 113 114 @Nullable private PrefixDelegation mAdvertise; 115 @Nullable private PrefixDelegation mReply; 116 @Nullable private byte[] mServerDuid; 117 118 // State variables. 119 @NonNull private final Dependencies mDependencies; 120 @NonNull private final Context mContext; 121 @NonNull private final Random mRandom; 122 @NonNull private final StateMachine mController; 123 @NonNull private final WakeupMessage mKickAlarm; 124 @NonNull private final WakeupMessage mRenewAlarm; 125 @NonNull private final WakeupMessage mRebindAlarm; 126 @NonNull private final WakeupMessage mExpiryAlarm; 127 @NonNull private final InterfaceParams mIface; 128 @NonNull private final Dhcp6PacketHandler mDhcp6PacketHandler; 129 @NonNull private final byte[] mClientDuid; 130 131 // States. 132 private State mStoppedState = new StoppedState(); 133 private State mStartedState = new StartedState(); 134 private State mSolicitState = new SolicitState(); 135 private State mRequestState = new RequestState(); 136 private State mHaveLeaseState = new HaveLeaseState(); 137 private State mBoundState = new BoundState(); 138 private State mRenewState = new RenewState(); 139 private State mRebindState = new RebindState(); 140 141 /** 142 * Encapsulates Dhcp6Client depencencies that's used for unit testing and 143 * integration testing. 144 */ 145 public static class Dependencies { 146 /** 147 * Read an integer DeviceConfig property. 148 */ getDeviceConfigPropertyInt(String name, int defaultValue)149 public int getDeviceConfigPropertyInt(String name, int defaultValue) { 150 return DeviceConfigUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name, 151 defaultValue); 152 } 153 } 154 makeWakeupMessage(String cmdName, int cmd)155 private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { 156 cmdName = Dhcp6Client.class.getSimpleName() + "." + mIface.name + "." + cmdName; 157 return new WakeupMessage(mContext, getHandler(), cmdName, cmd); 158 } 159 Dhcp6Client(@onNull final Context context, @NonNull final StateMachine controller, @NonNull final InterfaceParams iface, @NonNull final Dependencies deps)160 private Dhcp6Client(@NonNull final Context context, @NonNull final StateMachine controller, 161 @NonNull final InterfaceParams iface, @NonNull final Dependencies deps) { 162 super(TAG, controller.getHandler()); 163 164 mDependencies = deps; 165 mContext = context; 166 mController = controller; 167 mIface = iface; 168 mClientDuid = Dhcp6Packet.createClientDuid(iface.macAddr); 169 mDhcp6PacketHandler = new Dhcp6PacketHandler(getHandler()); 170 171 addState(mStoppedState); 172 addState(mStartedState); { 173 addState(mSolicitState, mStartedState); 174 addState(mRequestState, mStartedState); 175 addState(mHaveLeaseState, mStartedState); { 176 addState(mBoundState, mHaveLeaseState); 177 addState(mRenewState, mHaveLeaseState); 178 addState(mRebindState, mHaveLeaseState); 179 } 180 } 181 182 setInitialState(mStoppedState); 183 184 mRandom = new Random(); 185 186 // Used to schedule packet retransmissions. 187 mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); 188 // Used to schedule DHCP reacquisition. 189 mRenewAlarm = makeWakeupMessage("RENEW", CMD_DHCP6_PD_RENEW); 190 mRebindAlarm = makeWakeupMessage("REBIND", CMD_DHCP6_PD_REBIND); 191 mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_DHCP6_PD_EXPIRE); 192 } 193 194 /** 195 * Make a Dhcp6Client instance. 196 */ makeDhcp6Client(@onNull final Context context, @NonNull final StateMachine controller, @NonNull final InterfaceParams ifParams, @NonNull final Dependencies deps)197 public static Dhcp6Client makeDhcp6Client(@NonNull final Context context, 198 @NonNull final StateMachine controller, @NonNull final InterfaceParams ifParams, 199 @NonNull final Dependencies deps) { 200 final Dhcp6Client client = new Dhcp6Client(context, controller, ifParams, deps); 201 client.start(); 202 return client; 203 } 204 205 /** 206 * Quit the Dhcp6 StateMachine. 207 * 208 * @hide 209 */ doQuit()210 public void doQuit() { 211 Log.d(TAG, "doQuit"); 212 quit(); 213 } 214 215 @Override onQuitting()216 protected void onQuitting() { 217 Log.d(TAG, "onQuitting"); 218 mController.sendMessage(CMD_ON_QUIT); 219 } 220 221 /** 222 * Retransmits packets per algorithm defined in RFC8415 section 15. Packet transmission is 223 * triggered by CMD_KICK, which is sent by an AlarmManager alarm. Kicks are cancelled when 224 * leaving the state. 225 * 226 * Concrete subclasses must initialize retransmission parameters and implement sendPacket, 227 * which is called when the alarm fires and a packet needs to be transmitted, and receivePacket, 228 * which is triggered by CMD_RECEIVED_PACKET sent by the receive thread. 229 */ 230 abstract class MessageExchangeState extends State { 231 private int mTransId = 0; 232 private long mTransStartMs = 0; 233 private long mMaxRetransTimeMs = 0; 234 235 private long mRetransTimeout = -1; 236 private int mRetransCount = 0; 237 private final long mInitialDelayMs; 238 private final long mInitialRetransTimeMs; 239 private final int mMaxRetransCount; 240 private final IntSupplier mMaxRetransTimeSupplier; 241 MessageExchangeState(final int delay, final int irt, final int mrc, final IntSupplier mrt)242 MessageExchangeState(final int delay, final int irt, final int mrc, final IntSupplier mrt) { 243 mInitialDelayMs = delay; 244 mInitialRetransTimeMs = irt; 245 mMaxRetransCount = mrc; 246 mMaxRetransTimeSupplier = mrt; 247 } 248 249 @Override enter()250 public void enter() { 251 super.enter(); 252 mMaxRetransTimeMs = mMaxRetransTimeSupplier.getAsInt(); 253 // Every message exchange generates a new transaction id. 254 mTransId = mRandom.nextInt() & 0xffffff; 255 sendMessageDelayed(CMD_KICK, mInitialDelayMs); 256 } 257 handleKick()258 private void handleKick() { 259 // rfc8415#section-21.9: The elapsed time is measured from the time at which the 260 // client sent the first message in the message exchange, and the elapsed-time field 261 // is set to 0 in the first message in the message exchange. 262 final long elapsedTimeMs; 263 if (mRetransCount == 0) { 264 elapsedTimeMs = 0; 265 mTransStartMs = SystemClock.elapsedRealtime(); 266 } else { 267 elapsedTimeMs = SystemClock.elapsedRealtime() - mTransStartMs; 268 } 269 270 sendPacket(mTransId, elapsedTimeMs); 271 // Compares retransmission parameters and reschedules alarm accordingly. 272 scheduleKick(); 273 } 274 handleReceivedPacket(@onNull final Dhcp6Packet packet)275 private void handleReceivedPacket(@NonNull final Dhcp6Packet packet) { 276 // Technically it is valid for the server to not include a prefix in an IA in certain 277 // scenarios (specifically in a reply to Renew / Rebind, which means: do not extend the 278 // prefix, e.g. the list of prefix is empty). However, if prefix(es) do exist and all 279 // prefixes are invalid, then we should just ignore this packet. 280 if (!packet.isValid(mTransId, mClientDuid)) return; 281 if (!packet.mPrefixDelegation.ipos.isEmpty()) { 282 boolean allInvalidPrefixes = true; 283 for (IaPrefixOption ipo : packet.mPrefixDelegation.ipos) { 284 if (ipo != null && ipo.isValid()) { 285 allInvalidPrefixes = false; 286 break; 287 } 288 } 289 if (allInvalidPrefixes) { 290 Log.w(TAG, "All IA_Prefix options included in the " 291 + packet.getClass().getSimpleName() + " are invalid, ignore it."); 292 return; 293 } 294 } 295 receivePacket(packet); 296 } 297 298 @Override processMessage(Message message)299 public boolean processMessage(Message message) { 300 if (super.processMessage(message) == HANDLED) { 301 return HANDLED; 302 } 303 304 switch (message.what) { 305 case CMD_KICK: 306 handleKick(); 307 return HANDLED; 308 case CMD_RECEIVED_PACKET: 309 handleReceivedPacket((Dhcp6Packet) message.obj); 310 return HANDLED; 311 default: 312 return NOT_HANDLED; 313 } 314 } 315 316 @Override exit()317 public void exit() { 318 super.exit(); 319 mKickAlarm.cancel(); 320 mRetransTimeout = -1; 321 mRetransCount = 0; 322 mMaxRetransTimeMs = 0; 323 } 324 sendPacket(int transId, long elapsedTimeMs)325 protected abstract boolean sendPacket(int transId, long elapsedTimeMs); receivePacket(Dhcp6Packet packet)326 protected abstract void receivePacket(Dhcp6Packet packet); 327 // If the message exchange is considered to have failed according to the retransmission 328 // mechanism(i.e. client has transmitted the message MRC times or MRD seconds has elapsed 329 // since the first message transmission), this method will be called to roll back to Solicit 330 // state and restart the configuration, and notify IpClient the DHCPv6 message exchange 331 // failure if needed. onMessageExchangeFailed()332 protected void onMessageExchangeFailed() {} 333 334 /** 335 * Per RFC8415 section 15, each of the computations of a new RT includes a randomization 336 * factor (RAND), which is a random number chosen with a uniform distribution between -0.1 337 * and +0.1. 338 */ rand()339 private double rand() { 340 return mRandom.nextDouble() / 5 - 0.1; 341 } 342 scheduleKick()343 protected void scheduleKick() { 344 if (mRetransTimeout == -1) { 345 // RT for the first message transmission is based on IRT. 346 mRetransTimeout = mInitialRetransTimeMs + (long) (rand() * mInitialRetransTimeMs); 347 } else { 348 // RT for each subsequent message transmission is based on the previous value of RT. 349 mRetransTimeout = 2 * mRetransTimeout + (long) (rand() * mRetransTimeout); 350 } 351 if (mMaxRetransTimeMs != 0 && mRetransTimeout > mMaxRetransTimeMs) { 352 mRetransTimeout = mMaxRetransTimeMs + (long) (rand() * mMaxRetransTimeMs); 353 } 354 // Per RFC8415 section 18.2.4 and 18.2.5, MRD equals to the remaining time until 355 // earliest T2(RenewState) or valid lifetimes of all leases in all IA have expired 356 // (RebindState), and message exchange is terminated when the earliest time T2 is 357 // reached, at which point client begins the Rebind message exchange, however, section 358 // 15 says the message exchange fails(terminated) once MRD seconds have elapsed since 359 // the client first transmitted the message. So far MRD is being used for Renew, Rebind 360 // and Confirm message retransmission. Given we don't support Confirm message yet, we 361 // can just use rebindTimeout and expirationTimeout on behalf of MRD which have been 362 // scheduled in BoundState to simplify the implementation, therefore, we don't need to 363 // explicitly assign the MRD in the subclasses. 364 if (mMaxRetransCount != 0 && mRetransCount > mMaxRetransCount) { 365 onMessageExchangeFailed(); 366 Log.i(TAG, "client has transmitted the message " + mMaxRetransCount 367 + " times, stopping retransmission"); 368 return; 369 } 370 mKickAlarm.schedule(SystemClock.elapsedRealtime() + mRetransTimeout); 371 mRetransCount++; 372 } 373 } 374 scheduleLeaseTimers()375 private void scheduleLeaseTimers() { 376 // TODO: validate t1, t2, valid and preferred lifetimes before the timers are scheduled 377 // to prevent packet storms due to low timeouts. Preferred/valid lifetime of 0 should be 378 // excluded before scheduling the lease timer. 379 int renewTimeout = mReply.t1; 380 int rebindTimeout = mReply.t2; 381 final long deprecationTimeout = mReply.getMinimalPreferredLifetime(); 382 final long expirationTimeout = mReply.getMinimalValidLifetime(); 383 384 // rfc8415#section-14.2: if t1 and / or t2 are 0, the client chooses an appropriate value. 385 // rfc8415#section-21.21: Recommended values for T1 and T2 are 0.5 and 0.8 times the 386 // shortest preferred lifetime of the prefixes in the IA_PD that the server is willing to 387 // extend, respectively. 388 if (renewTimeout == 0) { 389 renewTimeout = (int) (deprecationTimeout * 0.5); 390 } 391 if (rebindTimeout == 0) { 392 rebindTimeout = (int) (deprecationTimeout * 0.8); 393 } 394 395 // Note: message validation asserts that the received t1 <= t2 if both t1 > 0 and t2 > 0. 396 // However, if t1 or t2 are 0, it is possible for renewTimeout to become larger than 397 // rebindTimeout (and similarly, rebindTimeout to become larger than expirationTimeout). 398 // For example: t1 = 0, t2 = 40, valid lft = 100 results in renewTimeout = 50, and 399 // rebindTimeout = 40. Hence, their correct order must be asserted below. 400 401 // If timeouts happen to coincide or are out of order, the former (in respect to the 402 // specified provisioning lifecycle) can be skipped. This also takes care of the case where 403 // the server sets t1 == t2 == valid lft, which indicates that the IA cannot be renewed, so 404 // there is no point in trying. 405 if (renewTimeout >= rebindTimeout) { 406 // skip RENEW 407 renewTimeout = 0; 408 } 409 if (rebindTimeout >= expirationTimeout) { 410 // skip REBIND 411 rebindTimeout = 0; 412 } 413 414 final long now = SystemClock.elapsedRealtime(); 415 if (renewTimeout > 0) { 416 mRenewAlarm.schedule(now + renewTimeout * (long) SECONDS); 417 Log.d(TAG, "Scheduling IA_PD renewal in " + renewTimeout + "s"); 418 } 419 if (rebindTimeout > 0) { 420 mRebindAlarm.schedule(now + rebindTimeout * (long) SECONDS); 421 Log.d(TAG, "Scheduling IA_PD rebind in " + rebindTimeout + "s"); 422 } 423 mExpiryAlarm.schedule(now + expirationTimeout * (long) SECONDS); 424 Log.d(TAG, "Scheduling IA_PD expiry in " + expirationTimeout + "s"); 425 } 426 notifyPrefixDelegation(int result, @Nullable final List<IaPrefixOption> ipos)427 private void notifyPrefixDelegation(int result, @Nullable final List<IaPrefixOption> ipos) { 428 mController.sendMessage(CMD_DHCP6_RESULT, result, 0, ipos); 429 } 430 clearDhcp6State()431 private void clearDhcp6State() { 432 mAdvertise = null; 433 mReply = null; 434 mServerDuid = null; 435 mSolMaxRtMs = SOL_MAX_RT; 436 } 437 438 @SuppressWarnings("ByteBufferBackingArray") sendSolicitPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd)439 private boolean sendSolicitPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { 440 final ByteBuffer packet = Dhcp6Packet.buildSolicitPacket(transId, elapsedTimeMs, 441 iapd.array(), mClientDuid, true /* rapidCommit */); 442 return transmitPacket(packet, "solicit"); 443 } 444 445 @SuppressWarnings("ByteBufferBackingArray") sendRequestPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd)446 private boolean sendRequestPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { 447 final ByteBuffer packet = Dhcp6Packet.buildRequestPacket(transId, elapsedTimeMs, 448 iapd.array(), mClientDuid, mServerDuid); 449 return transmitPacket(packet, "request"); 450 } 451 452 @SuppressWarnings("ByteBufferBackingArray") sendRenewPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd)453 private boolean sendRenewPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { 454 final ByteBuffer packet = Dhcp6Packet.buildRenewPacket(transId, elapsedTimeMs, 455 iapd.array(), mClientDuid, mServerDuid); 456 return transmitPacket(packet, "renew"); 457 } 458 459 @SuppressWarnings("ByteBufferBackingArray") sendRebindPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd)460 private boolean sendRebindPacket(int transId, long elapsedTimeMs, final ByteBuffer iapd) { 461 final ByteBuffer packet = Dhcp6Packet.buildRebindPacket(transId, elapsedTimeMs, 462 iapd.array(), mClientDuid); 463 return transmitPacket(packet, "rebind"); 464 } 465 466 /** 467 * Parent state at which client does initialization of interface and packet handler, also 468 * processes the CMD_STOP_DHCP6 command in this state which child states don't handle. 469 */ 470 class StartedState extends State { 471 @Override enter()472 public void enter() { 473 clearDhcp6State(); 474 if (mDhcp6PacketHandler.start()) return; 475 Log.e(TAG, "Fail to start DHCPv6 Packet Handler"); 476 // We cannot call transitionTo because a transition is still in progress. 477 // Instead, ensure that we process CMD_STOP_DHCP6 as soon as the transition is complete. 478 deferMessage(obtainMessage(CMD_STOP_DHCP6)); 479 } 480 481 @Override exit()482 public void exit() { 483 mDhcp6PacketHandler.stop(); 484 if (DBG) Log.d(TAG, "DHCPv6 Packet Handler stopped"); 485 clearDhcp6State(); 486 } 487 488 @Override processMessage(Message message)489 public boolean processMessage(Message message) { 490 super.processMessage(message); 491 switch (message.what) { 492 case CMD_STOP_DHCP6: 493 transitionTo(mStoppedState); 494 return HANDLED; 495 default: 496 return NOT_HANDLED; 497 } 498 } 499 } 500 501 /** 502 * Initial state of DHCPv6 state machine. 503 */ 504 class StoppedState extends State { 505 @Override processMessage(Message message)506 public boolean processMessage(Message message) { 507 switch (message.what) { 508 case CMD_START_DHCP6: 509 // TODO: store the delegated prefix in IpMemoryStore and start in REBIND instead 510 // of SOLICIT if there is already a valid prefix on this network. 511 transitionTo(mSolicitState); 512 return HANDLED; 513 default: 514 return NOT_HANDLED; 515 } 516 } 517 } 518 519 /** 520 * Client (re)transmits a Solicit message to locate DHCPv6 servers and processes the Advertise 521 * message in this state. 522 * 523 * Note: Not implement DHCPv6 server selection, always request the first Advertise we receive. 524 */ 525 class SolicitState extends MessageExchangeState { SolicitState()526 SolicitState() { 527 // First Solicit message should be delayed by a random amount of time between 0 528 // and SOL_MAX_DELAY(1s). 529 super((int) (new Random().nextDouble() * SECONDS) /* delay */, SOL_TIMEOUT /* IRT */, 530 0 /* MRC */, () -> mSolMaxRtMs /* MRT */); 531 } 532 533 @Override enter()534 public void enter() { 535 super.enter(); 536 } 537 538 @Override sendPacket(int transId, long elapsedTimeMs)539 protected boolean sendPacket(int transId, long elapsedTimeMs) { 540 final IaPrefixOption hintOption = new IaPrefixOption((short) IaPrefixOption.LENGTH, 541 0 /* preferred */, 0 /* valid */, (byte) RFC7421_PREFIX_LENGTH, 542 new byte[16] /* empty prefix */); 543 final PrefixDelegation pd = new PrefixDelegation(IAID, 0 /* t1 */, 0 /* t2 */, 544 Collections.singletonList(hintOption)); 545 return sendSolicitPacket(transId, elapsedTimeMs, pd.build()); 546 } 547 548 @Override receivePacket(Dhcp6Packet packet)549 protected void receivePacket(Dhcp6Packet packet) { 550 final PrefixDelegation pd = packet.mPrefixDelegation; 551 // Ignore any Advertise or Reply for Solicit(with Rapid Commit) with NoPrefixAvail 552 // status code, retransmit Solicit to see if any valid response from other Servers. 553 if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { 554 Log.w(TAG, "Server responded to Solicit without available prefix, ignoring"); 555 return; 556 } 557 if (packet instanceof Dhcp6AdvertisePacket) { 558 Log.d(TAG, "Get prefix delegation option from Advertise: " + pd); 559 mAdvertise = pd; 560 mServerDuid = packet.mServerDuid; 561 mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); 562 transitionTo(mRequestState); 563 } else if (packet instanceof Dhcp6ReplyPacket) { 564 if (!packet.mRapidCommit) { 565 Log.e(TAG, "Server responded to Solicit with Reply without rapid commit option" 566 + ", ignoring"); 567 return; 568 } 569 Log.d(TAG, "Get prefix delegation option from RapidCommit Reply: " + pd); 570 mReply = pd; 571 mServerDuid = packet.mServerDuid; 572 mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); 573 transitionTo(mBoundState); 574 } 575 } 576 } 577 578 /** 579 * Client (re)transmits a Request message to request configuration from a specific server and 580 * process the Reply message in this state. 581 */ 582 class RequestState extends MessageExchangeState { RequestState()583 RequestState() { 584 super(0 /* delay */, REQ_TIMEOUT /* IRT */, REQ_MAX_RC /* MRC */, 585 () -> REQ_MAX_RT /* MRT */); 586 } 587 588 @Override sendPacket(int transId, long elapsedTimeMs)589 protected boolean sendPacket(int transId, long elapsedTimeMs) { 590 return sendRequestPacket(transId, elapsedTimeMs, mAdvertise.build()); 591 } 592 593 @Override receivePacket(Dhcp6Packet packet)594 protected void receivePacket(Dhcp6Packet packet) { 595 if (!(packet instanceof Dhcp6ReplyPacket)) return; 596 final PrefixDelegation pd = packet.mPrefixDelegation; 597 if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { 598 Log.w(TAG, "Server responded to Request without available prefix, restart Solicit"); 599 transitionTo(mSolicitState); 600 return; 601 } 602 Log.d(TAG, "Get prefix delegation option from Reply: " + pd); 603 mReply = pd; 604 mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); 605 transitionTo(mBoundState); 606 } 607 608 @Override onMessageExchangeFailed()609 protected void onMessageExchangeFailed() { 610 transitionTo(mSolicitState); 611 } 612 } 613 614 /** 615 * Parent state of other states at which client has already obtained the lease from server. 616 */ 617 class HaveLeaseState extends State { 618 @Override processMessage(Message message)619 public boolean processMessage(Message message) { 620 switch (message.what) { 621 case CMD_DHCP6_PD_EXPIRE: 622 notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, mReply.getValidIaPrefixes()); 623 transitionTo(mSolicitState); 624 return HANDLED; 625 default: 626 return NOT_HANDLED; 627 } 628 } 629 630 @Override exit()631 public void exit() { 632 // Clear any extant alarms. 633 mRenewAlarm.cancel(); 634 mRebindAlarm.cancel(); 635 mExpiryAlarm.cancel(); 636 clearDhcp6State(); 637 } 638 } 639 640 /** 641 * Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound 642 * state until T1 expires, and then transition to Renew state to extend the lease duration. 643 */ 644 class BoundState extends State { 645 @Override enter()646 public void enter() { 647 super.enter(); 648 scheduleLeaseTimers(); 649 // Pass valid delegated prefix(es) to IpClient for IPv6 address configuration and 650 // active prefix(es) maintenance. 651 notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply.getValidIaPrefixes()); 652 } 653 654 @Override processMessage(Message message)655 public boolean processMessage(Message message) { 656 super.processMessage(message); 657 switch (message.what) { 658 case CMD_DHCP6_PD_RENEW: 659 transitionTo(mRenewState); 660 return HANDLED; 661 default: 662 return NOT_HANDLED; 663 } 664 } 665 } 666 667 668 /** 669 * Per RFC8415 section 18.2.10.1: Reply for renew or Rebind. 670 * - If all binding IA_PDs were renewed/rebound(so far we only support one IA_PD option per 671 * interface), then move to BoundState to update the existing global IPv6 addresses lifetime 672 * or install new global IPv6 address depending on the response from server. 673 * - Server may add new IA prefix option in Reply message(e.g. due to renumbering events), or 674 * may choose to deprecate some prefixes if it cannot extend the lifetime by: 675 * - either not including these requested IA prefixes in Reply message 676 * - or setting the valid lifetime equals to T1/T2 677 * That forces previous delegated prefixes to expire in a natural way, and client should 678 * also stop trying to extend the lifetime for them. That being said, the global IPv6 address 679 * lifetime won't be updated in BoundState if corresponding prefix doesn't appear in Reply 680 * message, resulting in these global IPv6 addresses expire eventually and IpClient obtains 681 * these updates via netlink message and remove the delegated prefix(es) from LinkProperties. 682 * - If some binding IA_PDs were absent in Reply message, client should still stay at RenewState 683 * or RebindState and retransmit Renew/Rebind messages to see if it can get all later. So far 684 * we only support one IA_PD option per interface, if the received Reply message doesn't take 685 * any IA_Prefix option, then treat it as if IA_PD is absent, since there's no point in 686 * returning BoundState again. 687 */ 688 abstract class ReacquireState extends MessageExchangeState { ReacquireState(final int irt, final int mrt)689 ReacquireState(final int irt, final int mrt) { 690 super(0 /* delay */, irt, 0 /* MRC */, () -> mrt /* MRT */); 691 } 692 693 @Override enter()694 public void enter() { 695 super.enter(); 696 } 697 698 @Override receivePacket(Dhcp6Packet packet)699 protected void receivePacket(Dhcp6Packet packet) { 700 if (!(packet instanceof Dhcp6ReplyPacket)) return; 701 final PrefixDelegation pd = packet.mPrefixDelegation; 702 // Stay at Renew/Rebind state if the Reply message takes NoPrefixAvail status code, 703 // retransmit Renew/Rebind message to server, to retry obtaining the prefixes. 704 if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { 705 Log.w(TAG, "Server responded to Renew/Rebind without available prefix, ignoring"); 706 return; 707 } 708 // TODO: send a Request message to the server that responded if any of the IA_PDs in 709 // Reply message contain NoBinding status code. 710 Log.d(TAG, "Get prefix delegation option from Reply as response to Renew/Rebind " + pd); 711 if (pd.ipos.isEmpty()) return; 712 mReply = pd; 713 mServerDuid = packet.mServerDuid; 714 // Once the delegated prefix gets refreshed successfully we have to extend the 715 // preferred lifetime and valid lifetime of global IPv6 addresses, otherwise 716 // these addresses will become depreacated finally and then provisioning failure 717 // happens. So we transit to mBoundState to update the address with refreshed 718 // preferred and valid lifetime via sending RTM_NEWADDR message, going back to 719 // Bound state after a success update. 720 transitionTo(mBoundState); 721 } 722 } 723 724 /** 725 * Client enters Renew state when T1 expires and (re)transmits Renew message to the 726 * server that originally provided the client's leases and configuration parameters to 727 * extend the lifetimes on the leases assigned to the client. 728 */ 729 class RenewState extends ReacquireState { RenewState()730 RenewState() { 731 super(REN_TIMEOUT, REN_MAX_RT); 732 } 733 734 @Override processMessage(Message message)735 public boolean processMessage(Message message) { 736 if (super.processMessage(message) == HANDLED) { 737 return HANDLED; 738 } 739 switch (message.what) { 740 case CMD_DHCP6_PD_REBIND: 741 transitionTo(mRebindState); 742 return HANDLED; 743 default: 744 return NOT_HANDLED; 745 } 746 } 747 748 @Override sendPacket(int transId, long elapsedTimeMs)749 protected boolean sendPacket(int transId, long elapsedTimeMs) { 750 final List<IaPrefixOption> toBeRenewed = mReply.getRenewableIaPrefixes(); 751 if (toBeRenewed.isEmpty()) { 752 if (DBG) Log.d(TAG, "Do not send Renew message due to no renewable prefix."); 753 return false; 754 } 755 return sendRenewPacket(transId, elapsedTimeMs, mReply.build(toBeRenewed)); 756 } 757 } 758 759 /** 760 * Client enters Rebind state when T2 expires and (re)transmits Rebind message to any 761 * available server to extend the lifetimes on the leases assigned to the client and to 762 * update other configuration parameters. 763 */ 764 class RebindState extends ReacquireState { RebindState()765 RebindState() { 766 super(REB_TIMEOUT, REB_MAX_RT); 767 } 768 769 @Override sendPacket(int transId, long elapsedTimeMs)770 protected boolean sendPacket(int transId, long elapsedTimeMs) { 771 final List<IaPrefixOption> toBeRebound = mReply.getRenewableIaPrefixes(); 772 if (toBeRebound.isEmpty()) { 773 if (DBG) Log.d(TAG, "Do not send Rebind message due to no renewable prefix."); 774 return false; 775 } 776 return sendRebindPacket(transId, elapsedTimeMs, mReply.build(toBeRebound)); 777 } 778 } 779 780 private class Dhcp6PacketHandler extends PacketReader { 781 private FileDescriptor mUdpSock; 782 Dhcp6PacketHandler(Handler handler)783 Dhcp6PacketHandler(Handler handler) { 784 super(handler); 785 } 786 787 @Override handlePacket(byte[] recvbuf, int length)788 protected void handlePacket(byte[] recvbuf, int length) { 789 try { 790 final Dhcp6Packet packet = Dhcp6Packet.decode(recvbuf, length); 791 if (DBG) Log.d(TAG, "Received packet: " + packet); 792 sendMessage(CMD_RECEIVED_PACKET, packet); 793 } catch (Dhcp6Packet.ParseException e) { 794 Log.e(TAG, "Can't parse DHCPv6 packet: " + e.getMessage()); 795 } 796 } 797 798 @Override createFd()799 protected FileDescriptor createFd() { 800 try { 801 mUdpSock = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); 802 SocketUtils.bindSocketToInterface(mUdpSock, mIface.name); 803 Os.bind(mUdpSock, IPV6_ADDR_ANY, DHCP6_CLIENT_PORT); 804 } catch (SocketException | ErrnoException e) { 805 Log.e(TAG, "Error creating udp socket", e); 806 closeFd(mUdpSock); 807 mUdpSock = null; 808 return null; 809 } 810 return mUdpSock; 811 } 812 transmitPacket(final ByteBuffer buf)813 public int transmitPacket(final ByteBuffer buf) throws ErrnoException, SocketException { 814 int ret = Os.sendto(mUdpSock, buf.array(), 0 /* byteOffset */, 815 buf.limit() /* byteCount */, 0 /* flags */, ALL_DHCP_RELAY_AGENTS_AND_SERVERS, 816 DHCP6_SERVER_PORT); 817 return ret; 818 } 819 } 820 821 @SuppressWarnings("ByteBufferBackingArray") transmitPacket(@onNull final ByteBuffer buf, @NonNull final String description)822 private boolean transmitPacket(@NonNull final ByteBuffer buf, 823 @NonNull final String description) { 824 try { 825 if (DBG) { 826 Log.d(TAG, "Multicasting " + description + " to ff02::1:2" + " packet raw data: " 827 + HexDump.toHexString(buf.array(), 0, buf.limit())); 828 } 829 mDhcp6PacketHandler.transmitPacket(buf); 830 } catch (ErrnoException | IOException e) { 831 Log.e(TAG, "Can't send packet: ", e); 832 return false; 833 } 834 return true; 835 } 836 } 837