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