dhcp_client_fsm.c
Go to the documentation of this file.
1 /**
2  * @file dhcp_client_fsm.c
3  * @brief DHCP client finite state machine
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2026 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.6.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL DHCP_TRACE_LEVEL
33 
34 //Dependencies
35 #include "core/net.h"
36 #include "dhcp/dhcp_client.h"
37 #include "dhcp/dhcp_client_fsm.h"
38 #include "dhcp/dhcp_client_misc.h"
39 #include "mdns/mdns_responder.h"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (IPV4_SUPPORT == ENABLED && DHCP_CLIENT_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief INIT state
48  *
49  * This is the initialization state, where a client begins the process of
50  * acquiring a lease. It also returns here when a lease ends, or when a
51  * lease negotiation fails
52  *
53  * @param[in] context Pointer to the DHCP client context
54  **/
55 
57 {
58  systime_t delay;
59 
60  //Check whether the DHCP client is running
61  if(context->running)
62  {
63  //Wait for the link to be up before starting DHCP configuration
64  if(context->interface->linkState)
65  {
66  //The client should wait for a random time to desynchronize the use of
67  //DHCP at startup
68  delay = netGenerateRandRange(context->netContext, 0,
70 
71  //Record the time at which the client started the address acquisition
72  //process
73  context->configStartTime = osGetSystemTime();
74  //Clear flag
75  context->timeoutEventDone = FALSE;
76 
77  //Switch to the SELECTING state
79  }
80  }
81 }
82 
83 
84 /**
85  * @brief SELECTING state
86  *
87  * The client is waiting to receive DHCPOFFER messages from one or more DHCP
88  * servers, so it can choose one
89  *
90  * @param[in] context Pointer to the DHCP client context
91  **/
92 
94 {
96 
97  //Get current time
99 
100  //Check current time
101  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
102  {
103  //Check retransmission counter
104  if(context->retransmitCount == 0)
105  {
106  //The client generates and records a random transaction identifier
107  //(refer to RFC 2131, section 4.4.1)
108  context->transactionId = netGenerateRand(context->netContext);
109 
110  //Send a DHCPDISCOVER message
111  dhcpClientSendDiscover(context);
112 
113  //Initial timeout value
114  context->retransmitTimeout = DHCP_CLIENT_DISCOVER_INIT_RT;
115  }
116  else
117  {
118  //Send a DHCPDISCOVER message
119  dhcpClientSendDiscover(context);
120 
121  //The timeout value is doubled for each subsequent retransmission
122  context->retransmitTimeout *= 2;
123 
124  //Limit the timeout value to a maximum of 64 seconds
125  if(context->retransmitTimeout > DHCP_CLIENT_DISCOVER_MAX_RT)
126  {
127  context->retransmitTimeout = DHCP_CLIENT_DISCOVER_MAX_RT;
128  }
129  }
130 
131  //Save the time at which the message was sent
132  context->timestamp = time;
133 
134  //The timeout value should be randomized by the value of a uniform
135  //number chosen from the range -1 to +1
136  context->timeout = netGenerateRandRange(context->netContext,
137  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
138  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
139 
140  //Increment retransmission counter
141  context->retransmitCount++;
142  }
143 
144  //Manage DHCP configuration timeout
145  dhcpClientCheckTimeout(context);
146 }
147 
148 
149 /**
150  * @brief REQUESTING state
151  *
152  * The client is waiting to hear back from the server to which it sent its
153  * request
154  *
155  * @param[in] context Pointer to the DHCP client context
156  **/
157 
159 {
160  systime_t time;
161 
162  //Get current time
163  time = osGetSystemTime();
164 
165  //Check current time
166  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
167  {
168  //Check retransmission counter
169  if(context->retransmitCount == 0)
170  {
171  //The DHCPREQUEST message contains the same transaction identifier as
172  //the DHCPOFFER message (refer to RFC 2131, section 4.4.1)
173  dhcpClientSendRequest(context);
174 
175  //Initial timeout value
176  context->retransmitTimeout = DHCP_CLIENT_REQUEST_INIT_RT;
177 
178  //Save the time at which the message was sent
179  context->timestamp = time;
180 
181  //The timeout value should be randomized by the value of a uniform
182  //number chosen from the range -1 to +1
183  context->timeout = netGenerateRandRange(context->netContext,
184  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
185  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
186 
187  //Increment retransmission counter
188  context->retransmitCount++;
189  }
190  else if(context->retransmitCount < DHCP_CLIENT_REQUEST_MAX_RC)
191  {
192  //Send a DHCPREQUEST message
193  dhcpClientSendRequest(context);
194 
195  //The timeout value is doubled for each subsequent retransmission
196  context->retransmitTimeout *= 2;
197 
198  //Limit the timeout value to a maximum of 64 seconds
199  if(context->retransmitTimeout > DHCP_CLIENT_REQUEST_MAX_RT)
200  {
201  context->retransmitTimeout = DHCP_CLIENT_REQUEST_MAX_RT;
202  }
203 
204  //Save the time at which the message was sent
205  context->timestamp = time;
206 
207  //The timeout value should be randomized by the value of a uniform
208  //number chosen from the range -1 to +1
209  context->timeout = netGenerateRandRange(context->netContext,
210  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
211  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
212 
213  //Increment retransmission counter
214  context->retransmitCount++;
215  }
216  else
217  {
218  //If the client does not receive a response within a reasonable
219  //period of time, then it restarts the initialization procedure
221  }
222  }
223 
224  //Manage DHCP configuration timeout
225  dhcpClientCheckTimeout(context);
226 }
227 
228 
229 /**
230  * @brief INIT-REBOOT state
231  *
232  * When a client that already has a valid lease starts up after a power-down
233  * or reboot, it starts here instead of the INIT state
234  *
235  * @param[in] context Pointer to the DHCP client context
236  **/
237 
239 {
240  systime_t delay;
241 
242  //Check whether the DHCP client is running
243  if(context->running)
244  {
245  //Wait for the link to be up before starting DHCP configuration
246  if(context->interface->linkState)
247  {
248  //The client should wait for a random time to desynchronize the use of
249  //DHCP at startup
250  delay = netGenerateRandRange(context->netContext, 0,
252 
253  //Record the time at which the client started the address acquisition
254  //process
255  context->configStartTime = osGetSystemTime();
256  //Clear flag
257  context->timeoutEventDone = FALSE;
258 
259  //Switch to the REBOOTING state
261  }
262  }
263 }
264 
265 
266 /**
267  * @brief REBOOTING state
268  *
269  * A client that has rebooted with an assigned address is waiting for a
270  * confirming reply from a server
271  *
272  * @param[in] context Pointer to the DHCP client context
273  **/
274 
276 {
277  systime_t time;
278 
279  //Get current time
280  time = osGetSystemTime();
281 
282  //Check current time
283  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
284  {
285  //Check retransmission counter
286  if(context->retransmitCount == 0)
287  {
288  //The client generates and records a random transaction identifier
289  //(refer to RFC 2131, section 4.4.2)
290  context->transactionId = netGenerateRand(context->netContext);
291 
292  //Send a DHCPREQUEST message
293  dhcpClientSendRequest(context);
294 
295  //Initial timeout value
296  context->retransmitTimeout = DHCP_CLIENT_REQUEST_INIT_RT;
297 
298  //Save the time at which the message was sent
299  context->timestamp = time;
300 
301  //The timeout value should be randomized by the value of a uniform
302  //number chosen from the range -1 to +1
303  context->timeout = netGenerateRandRange(context->netContext,
304  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
305  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
306 
307  //Increment retransmission counter
308  context->retransmitCount++;
309  }
310  else if(context->retransmitCount < DHCP_CLIENT_REQUEST_MAX_RC)
311  {
312  //Send a DHCPREQUEST message
313  dhcpClientSendRequest(context);
314 
315  //The timeout value is doubled for each subsequent retransmission
316  context->retransmitTimeout *= 2;
317 
318  //Limit the timeout value to a maximum of 64 seconds
319  if(context->retransmitTimeout > DHCP_CLIENT_REQUEST_MAX_RT)
320  {
321  context->retransmitTimeout = DHCP_CLIENT_REQUEST_MAX_RT;
322  }
323 
324  //Save the time at which the message was sent
325  context->timestamp = time;
326 
327  //The timeout value should be randomized by the value of a uniform
328  //number chosen from the range -1 to +1
329  context->timeout = netGenerateRandRange(context->netContext,
330  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
331  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
332 
333  //Increment retransmission counter
334  context->retransmitCount++;
335  }
336  else
337  {
338  //If the client does not receive a response within a reasonable
339  //period of time, then it restarts the initialization procedure
341  }
342  }
343 
344  //Manage DHCP configuration timeout
345  dhcpClientCheckTimeout(context);
346 }
347 
348 
349 /**
350  * @brief PROBING state
351  *
352  * The client probes the newly received address
353  *
354  * @param[in] context Pointer to the DHCP client context
355  **/
356 
358 {
359  uint_t i;
360  systime_t time;
361  NetInterface *interface;
362 
363  //Point to the underlying network interface
364  interface = context->interface;
365  //Index of the IP address in the list of addresses assigned to the interface
366  i = context->ipAddrIndex;
367 
368  //Get current time
369  time = osGetSystemTime();
370 
371  //Check current time
372  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
373  {
374  //The address is already in use?
375  if(interface->ipv4Context.addrList[i].conflict)
376  {
377  //Select a new transaction identifier
378  context->transactionId = netGenerateRand(context->netContext);
379 
380  //If the client detects that the address is already in use, the
381  //client must send a DHCPDECLINE message to the server and
382  //restarts the configuration process
383  dhcpClientSendDecline(context);
384 
385  //The client should wait a minimum of ten seconds before
386  //restarting the configuration process to avoid excessive
387  //network traffic in case of looping
389  }
390  //Probing is on-going?
391  else if(context->retransmitCount < DHCP_CLIENT_PROBE_NUM)
392  {
393  //Conflict detection is done using ARP probes
394  arpSendProbe(interface, interface->ipv4Context.addrList[i].addr);
395 
396  //Save the time at which the packet was sent
397  context->timestamp = time;
398  //Delay until repeated probe
399  context->timeout = DHCP_CLIENT_PROBE_DELAY;
400  //Increment retransmission counter
401  context->retransmitCount++;
402  }
403  //Probing is complete?
404  else
405  {
406  //The use of the IPv4 address is now unrestricted
407  interface->ipv4Context.addrList[i].state = IPV4_ADDR_STATE_VALID;
408 
409  //The client transitions to the ANNOUNCING state
411  }
412  }
413 }
414 
415 
416 /**
417  * @brief ANNOUNCING state
418  *
419  * The client announces its new IP address
420  *
421  * @param[in] context Pointer to the DHCP client context
422  **/
423 
425 {
426  uint_t i;
427  systime_t time;
428  NetInterface *interface;
429 
430  //Point to the underlying network interface
431  interface = context->interface;
432  //Index of the IP address in the list of addresses assigned to the interface
433  i = context->ipAddrIndex;
434 
435  //Get current time
436  time = osGetSystemTime();
437 
438  //Check current time
439  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
440  {
441  //Announcement is on-going?
442  if(context->retransmitCount < DHCP_CLIENT_ANNOUNCE_NUM)
443  {
444  //An ARP announcement is identical to an ARP probe, except that now
445  //the sender and target IP addresses are both set to the host's newly
446  //selected IPv4 address
447  arpSendRequest(interface, interface->ipv4Context.addrList[i].addr,
449 
450  //Save the time at which the packet was sent
451  context->timestamp = time;
452  //Delay until repeated probe
453  context->timeout = DHCP_CLIENT_ANNOUNCE_INTERVAL;
454  //Increment retransmission counter
455  context->retransmitCount++;
456  }
457 
458  //Announcing is complete?
459  if(context->retransmitCount >= DHCP_CLIENT_ANNOUNCE_NUM)
460  {
461 #if (MDNS_RESPONDER_SUPPORT == ENABLED)
462  //Restart mDNS probing process
463  mdnsResponderStartProbing(interface->mdnsResponderContext);
464 #endif
465  //Dump current DHCP configuration for debugging purpose
466  dhcpClientDumpConfig(context);
467 
468  //The client transitions to the BOUND state
470  }
471  }
472 }
473 
474 
475 /**
476  * @brief BOUND state
477  *
478  * Client has a valid lease and is in its normal operating state
479  *
480  * @param[in] context Pointer to the DHCP client context
481  **/
482 
484 {
485  systime_t t1;
486  systime_t time;
487 
488  //Get current time
489  time = osGetSystemTime();
490 
491  //A client will never attempt to extend the lifetime of the address when
492  //T1 set to 0xFFFFFFFF
493  if(context->t1 != DHCP_INFINITE_TIME)
494  {
495  //Convert T1 to milliseconds
496  if(context->t1 < (MAX_DELAY / 1000))
497  {
498  t1 = context->t1 * 1000;
499  }
500  else
501  {
502  t1 = MAX_DELAY;
503  }
504 
505  //Check the time elapsed since the lease was obtained
506  if(timeCompare(time, context->leaseStartTime + t1) >= 0)
507  {
508  //Record the time at which the client started the address renewal
509  //process
510  context->configStartTime = time;
511 
512  //Enter the RENEWING state
514  }
515  }
516 }
517 
518 
519 /**
520  * @brief RENEWING state
521  *
522  * Client is trying to renew its lease. It regularly sends DHCPREQUEST
523  * messages with the server that gave it its current lease specified, and
524  * waits for a reply
525  *
526  * @param[in] context Pointer to the DHCP client context
527  **/
528 
530 {
531  systime_t t2;
532  systime_t time;
533 
534  //Get current time
535  time = osGetSystemTime();
536 
537  //Check current time
538  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
539  {
540  //Convert T2 to milliseconds
541  if(context->t2 < (MAX_DELAY / 1000))
542  {
543  t2 = context->t2 * 1000;
544  }
545  else
546  {
547  t2 = MAX_DELAY;
548  }
549 
550  //Check whether T2 timer has expired
551  if(timeCompare(time, context->leaseStartTime + t2) < 0)
552  {
553  //First DHCPREQUEST message?
554  if(context->retransmitCount == 0)
555  {
556  //A transaction identifier is used by the client to match incoming
557  //DHCP messages with pending requests
558  context->transactionId = netGenerateRand(context->netContext);
559  }
560 
561  //Send a DHCPREQUEST message
562  dhcpClientSendRequest(context);
563 
564  //Save the time at which the message was sent
565  context->timestamp = time;
566 
567  //Compute the remaining time until T2 expires
568  context->timeout = context->leaseStartTime + t2 - time;
569 
570  //The client should wait one-half of the remaining time until T2, down to
571  //a minimum of 60 seconds, before retransmitting the DHCPREQUEST message
572  if(context->timeout > (2 * DHCP_CLIENT_REQUEST_MIN_DELAY))
573  {
574  context->timeout /= 2;
575  }
576 
577  //Increment retransmission counter
578  context->retransmitCount++;
579  }
580  else
581  {
582  //If no DHCPACK arrives before time T2, the client moves to REBINDING
584  }
585  }
586 }
587 
588 
589 /**
590  * @brief REBINDING state
591  *
592  * The client has failed to renew its lease with the server that originally
593  * granted it, and now seeks a lease extension with any server that can hear
594  * it. It periodically sends DHCPREQUEST messages with no server specified
595  * until it gets a reply or the lease ends
596  *
597  * @param[in] context Pointer to the DHCP client context
598  **/
599 
601 {
602  systime_t time;
603  systime_t leaseTime;
604 #if (MDNS_RESPONDER_SUPPORT == ENABLED)
605  NetInterface *interface;
606 
607  //Point to the underlying network interface
608  interface = context->interface;
609 #endif
610 
611  //Get current time
612  time = osGetSystemTime();
613 
614  //Check current time
615  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
616  {
617  //Convert the lease time to milliseconds
618  if(context->leaseTime < (MAX_DELAY / 1000))
619  {
620  leaseTime = context->leaseTime * 1000;
621  }
622  else
623  {
624  leaseTime = MAX_DELAY;
625  }
626 
627  //Check whether the lease has expired
628  if(timeCompare(time, context->leaseStartTime + leaseTime) < 0)
629  {
630  //First DHCPREQUEST message?
631  if(context->retransmitCount == 0)
632  {
633  //A transaction identifier is used by the client to match incoming
634  //DHCP messages with pending requests
635  context->transactionId = netGenerateRand(context->netContext);
636  }
637 
638  //Send a DHCPREQUEST message
639  dhcpClientSendRequest(context);
640 
641  //Save the time at which the message was sent
642  context->timestamp = time;
643 
644  //Compute the remaining time until the lease expires
645  context->timeout = context->leaseStartTime + leaseTime - time;
646 
647  //The client should wait one-half of the remaining lease time, down to a
648  //minimum of 60 seconds, before retransmitting the DHCPREQUEST message
649  if(context->timeout > (2 * DHCP_CLIENT_REQUEST_MIN_DELAY))
650  {
651  context->timeout /= 2;
652  }
653 
654  //Increment retransmission counter
655  context->retransmitCount++;
656  }
657  else
658  {
659  //The host address is no longer valid
660  dhcpClientResetConfig(context);
661 
662 #if (MDNS_RESPONDER_SUPPORT == ENABLED)
663  //Restart mDNS probing process
664  mdnsResponderStartProbing(interface->mdnsResponderContext);
665 #endif
666  //If the lease expires before the client receives a DHCPACK, the client
667  //moves to INIT state
669  }
670  }
671 }
672 
673 #endif
#define DHCP_CLIENT_PROBE_DELAY
Definition: dhcp_client.h:112
DHCP client (Dynamic Host Configuration Protocol)
@ DHCP_STATE_ANNOUNCING
Definition: dhcp_client.h:165
void dhcpClientStateSelecting(DhcpClientContext *context)
SELECTING state.
void dhcpClientStateBound(DhcpClientContext *context)
BOUND state.
uint32_t netGenerateRand(NetContext *context)
Generate a random 32-bit value.
Definition: net_misc.c:956
#define DHCP_INFINITE_TIME
Definition: dhcp_common.h:53
void dhcpClientStateInitReboot(DhcpClientContext *context)
INIT-REBOOT state.
void dhcpClientStateInit(DhcpClientContext *context)
INIT state.
#define DHCP_CLIENT_REQUEST_MIN_DELAY
Definition: dhcp_client.h:98
error_t dhcpClientSendDiscover(DhcpClientContext *context)
Send DHCPDISCOVER message.
error_t dhcpClientSendRequest(DhcpClientContext *context)
Send DHCPREQUEST message.
#define timeCompare(t1, t2)
Definition: os_port.h:40
Helper functions for DHCP client.
void dhcpClientStateProbing(DhcpClientContext *context)
PROBING state.
void dhcpClientCheckTimeout(DhcpClientContext *context)
Manage DHCP configuration timeout.
#define DhcpClientContext
Definition: dhcp_client.h:145
#define FALSE
Definition: os_port.h:46
error_t dhcpClientSendDecline(DhcpClientContext *context)
Send DHCPDECLINE message.
void dhcpClientStateRebooting(DhcpClientContext *context)
REBOOTING state.
void dhcpClientStateAnnouncing(DhcpClientContext *context)
ANNOUNCING state.
void dhcpClientStateRenewing(DhcpClientContext *context)
RENEWING state.
#define NetInterface
Definition: net.h:40
#define DHCP_CLIENT_ANNOUNCE_INTERVAL
Definition: dhcp_client.h:126
void dhcpClientChangeState(DhcpClientContext *context, DhcpState newState, systime_t delay)
Update DHCP FSM state.
#define DHCP_CLIENT_ANNOUNCE_NUM
Definition: dhcp_client.h:119
uint32_t netGenerateRandRange(NetContext *context, uint32_t min, uint32_t max)
Generate a random value in the specified range.
Definition: net_misc.c:983
@ DHCP_STATE_REBOOTING
Definition: dhcp_client.h:163
uint32_t t2
@ DHCP_STATE_REBINDING
Definition: dhcp_client.h:168
#define DHCP_CLIENT_REQUEST_MAX_RC
Definition: dhcp_client.h:77
DHCP client finite state machine.
#define DHCP_CLIENT_REQUEST_MAX_RT
Definition: dhcp_client.h:91
void dhcpClientStateRebinding(DhcpClientContext *context)
REBINDING state.
error_t arpSendRequest(NetInterface *interface, Ipv4Addr targetIpAddr, const MacAddr *destMacAddr)
Send ARP request.
Definition: arp.c:979
uint32_t systime_t
System time.
#define DHCP_CLIENT_REQUEST_INIT_RT
Definition: dhcp_client.h:84
#define DHCP_CLIENT_INIT_DELAY
Definition: dhcp_client.h:56
@ DHCP_STATE_BOUND
Definition: dhcp_client.h:166
uint32_t time
uint32_t t1
@ DHCP_STATE_SELECTING
Definition: dhcp_client.h:160
void dhcpClientDumpConfig(DhcpClientContext *context)
Dump DHCP configuration for debugging purpose.
void dhcpClientStateRequesting(DhcpClientContext *context)
REQUESTING state.
#define DHCP_CLIENT_DISCOVER_MAX_RT
Definition: dhcp_client.h:70
#define DHCP_CLIENT_PROBE_NUM
Definition: dhcp_client.h:105
@ DHCP_STATE_INIT
Definition: dhcp_client.h:159
@ IPV4_ADDR_STATE_VALID
An address assigned to an interface whose use is unrestricted.
Definition: ipv4.h:228
#define MAX_DELAY
Definition: os_port.h:77
unsigned int uint_t
Definition: compiler_port.h:57
TCP/IP stack core.
#define DHCP_CLIENT_DISCOVER_INIT_RT
Definition: dhcp_client.h:63
error_t mdnsResponderStartProbing(MdnsResponderContext *context)
Restart probing process.
void dhcpClientResetConfig(DhcpClientContext *context)
Reset DHCP configuration.
Debugging facilities.
@ DHCP_STATE_RENEWING
Definition: dhcp_client.h:167
error_t arpSendProbe(NetInterface *interface, Ipv4Addr targetIpAddr)
Send ARP probe.
Definition: arp.c:919
const MacAddr MAC_BROADCAST_ADDR
Definition: ethernet.c:53
mDNS responder (Multicast DNS)
#define DHCP_CLIENT_RAND_FACTOR
Definition: dhcp_client.h:133
systime_t osGetSystemTime(void)
Retrieve system time.