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-2024 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.4.4
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  NetInterface *interface;
60 
61  //Point to the underlying network interface
62  interface = context->settings.interface;
63 
64  //Check whether the DHCP client is running
65  if(context->running)
66  {
67  //Wait for the link to be up before starting DHCP configuration
68  if(interface->linkState)
69  {
70  //The client should wait for a random time to desynchronize
71  //the use of DHCP at startup
73 
74  //Record the time at which the client started the address
75  //acquisition process
76  context->configStartTime = osGetSystemTime();
77  //Clear flag
78  context->timeoutEventDone = FALSE;
79 
80  //Switch to the SELECTING state
82  }
83  }
84 }
85 
86 
87 /**
88  * @brief SELECTING state
89  *
90  * The client is waiting to receive DHCPOFFER messages from
91  * one or more DHCP servers, so it can choose one
92  *
93  * @param[in] context Pointer to the DHCP client context
94  **/
95 
97 {
99 
100  //Get current time
101  time = osGetSystemTime();
102 
103  //Check current time
104  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
105  {
106  //Check retransmission counter
107  if(context->retransmitCount == 0)
108  {
109  //The client generates and records a random transaction identifier
110  //(refer to RFC 2131, section 4.4.1)
111  context->transactionId = netGenerateRand();
112 
113  //Send a DHCPDISCOVER message
114  dhcpClientSendDiscover(context);
115 
116  //Initial timeout value
117  context->retransmitTimeout = DHCP_CLIENT_DISCOVER_INIT_RT;
118  }
119  else
120  {
121  //Send a DHCPDISCOVER message
122  dhcpClientSendDiscover(context);
123 
124  //The timeout value is doubled for each subsequent retransmission
125  context->retransmitTimeout *= 2;
126 
127  //Limit the timeout value to a maximum of 64 seconds
128  if(context->retransmitTimeout > DHCP_CLIENT_DISCOVER_MAX_RT)
129  {
130  context->retransmitTimeout = DHCP_CLIENT_DISCOVER_MAX_RT;
131  }
132  }
133 
134  //Save the time at which the message was sent
135  context->timestamp = time;
136 
137  //The timeout value should be randomized by the value of a uniform
138  //number chosen from the range -1 to +1
139  context->timeout = netGenerateRandRange(
140  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
141  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
142 
143  //Increment retransmission counter
144  context->retransmitCount++;
145  }
146 
147  //Manage DHCP configuration timeout
148  dhcpClientCheckTimeout(context);
149 }
150 
151 
152 /**
153  * @brief REQUESTING state
154  *
155  * The client is waiting to hear back from the server to which it sent its
156  * request
157  *
158  * @param[in] context Pointer to the DHCP client context
159  **/
160 
162 {
163  systime_t time;
164 
165  //Get current time
166  time = osGetSystemTime();
167 
168  //Check current time
169  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
170  {
171  //Check retransmission counter
172  if(context->retransmitCount == 0)
173  {
174  //The DHCPREQUEST message contains the same transaction identifier as
175  //the DHCPOFFER message (refer to RFC 2131, section 4.4.1)
176  dhcpClientSendRequest(context);
177 
178  //Initial timeout value
179  context->retransmitTimeout = DHCP_CLIENT_REQUEST_INIT_RT;
180 
181  //Save the time at which the message was sent
182  context->timestamp = time;
183 
184  //The timeout value should be randomized by the value of a uniform
185  //number chosen from the range -1 to +1
186  context->timeout = netGenerateRandRange(
187  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
188  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
189 
190  //Increment retransmission counter
191  context->retransmitCount++;
192  }
193  else if(context->retransmitCount < DHCP_CLIENT_REQUEST_MAX_RC)
194  {
195  //Send a DHCPREQUEST message
196  dhcpClientSendRequest(context);
197 
198  //The timeout value is doubled for each subsequent retransmission
199  context->retransmitTimeout *= 2;
200 
201  //Limit the timeout value to a maximum of 64 seconds
202  if(context->retransmitTimeout > DHCP_CLIENT_REQUEST_MAX_RT)
203  {
204  context->retransmitTimeout = DHCP_CLIENT_REQUEST_MAX_RT;
205  }
206 
207  //Save the time at which the message was sent
208  context->timestamp = time;
209 
210  //The timeout value should be randomized by the value of a uniform
211  //number chosen from the range -1 to +1
212  context->timeout = netGenerateRandRange(
213  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
214  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
215 
216  //Increment retransmission counter
217  context->retransmitCount++;
218  }
219  else
220  {
221  //If the client does not receive a response within a reasonable
222  //period of time, then it restarts the initialization procedure
224  }
225  }
226 
227  //Manage DHCP configuration timeout
228  dhcpClientCheckTimeout(context);
229 }
230 
231 
232 /**
233  * @brief INIT-REBOOT state
234  *
235  * When a client that already has a valid lease starts up after a power-down
236  * or reboot, it starts here instead of the INIT state
237  *
238  * @param[in] context Pointer to the DHCP client context
239  **/
240 
242 {
243  systime_t delay;
244  NetInterface *interface;
245 
246  //Point to the underlying network interface
247  interface = context->settings.interface;
248 
249  //Check whether the DHCP client is running
250  if(context->running)
251  {
252  //Wait for the link to be up before starting DHCP configuration
253  if(interface->linkState)
254  {
255  //The client should wait for a random time to desynchronize
256  //the use of DHCP at startup
258 
259  //Record the time at which the client started the address
260  //acquisition process
261  context->configStartTime = osGetSystemTime();
262  //Clear flag
263  context->timeoutEventDone = FALSE;
264 
265  //Switch to the REBOOTING state
267  }
268  }
269 }
270 
271 
272 /**
273  * @brief REBOOTING state
274  *
275  * A client that has rebooted with an assigned address is waiting for a
276  * confirming reply from a server
277  *
278  * @param[in] context Pointer to the DHCP client context
279  **/
280 
282 {
283  systime_t time;
284 
285  //Get current time
286  time = osGetSystemTime();
287 
288  //Check current time
289  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
290  {
291  //Check retransmission counter
292  if(context->retransmitCount == 0)
293  {
294  //The client generates and records a random transaction identifier
295  //(refer to RFC 2131, section 4.4.2)
296  context->transactionId = netGenerateRand();
297 
298  //Send a DHCPREQUEST message
299  dhcpClientSendRequest(context);
300 
301  //Initial timeout value
302  context->retransmitTimeout = DHCP_CLIENT_REQUEST_INIT_RT;
303 
304  //Save the time at which the message was sent
305  context->timestamp = time;
306 
307  //The timeout value should be randomized by the value of a uniform
308  //number chosen from the range -1 to +1
309  context->timeout = netGenerateRandRange(
310  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
311  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
312 
313  //Increment retransmission counter
314  context->retransmitCount++;
315  }
316  else if(context->retransmitCount < DHCP_CLIENT_REQUEST_MAX_RC)
317  {
318  //Send a DHCPREQUEST message
319  dhcpClientSendRequest(context);
320 
321  //The timeout value is doubled for each subsequent retransmission
322  context->retransmitTimeout *= 2;
323 
324  //Limit the timeout value to a maximum of 64 seconds
325  if(context->retransmitTimeout > DHCP_CLIENT_REQUEST_MAX_RT)
326  {
327  context->retransmitTimeout = DHCP_CLIENT_REQUEST_MAX_RT;
328  }
329 
330  //Save the time at which the message was sent
331  context->timestamp = time;
332 
333  //The timeout value should be randomized by the value of a uniform
334  //number chosen from the range -1 to +1
335  context->timeout = netGenerateRandRange(
336  context->retransmitTimeout - DHCP_CLIENT_RAND_FACTOR,
337  context->retransmitTimeout + DHCP_CLIENT_RAND_FACTOR);
338 
339  //Increment retransmission counter
340  context->retransmitCount++;
341  }
342  else
343  {
344  //If the client does not receive a response within a reasonable
345  //period of time, then it restarts the initialization procedure
347  }
348  }
349 
350  //Manage DHCP configuration timeout
351  dhcpClientCheckTimeout(context);
352 }
353 
354 
355 /**
356  * @brief PROBING state
357  *
358  * The client probes the newly received address
359  *
360  * @param[in] context Pointer to the DHCP client context
361  **/
362 
364 {
365  uint_t i;
366  systime_t time;
367  NetInterface *interface;
368 
369  //Point to the underlying network interface
370  interface = context->settings.interface;
371  //Index of the IP address in the list of addresses assigned to the interface
372  i = context->settings.ipAddrIndex;
373 
374  //Get current time
375  time = osGetSystemTime();
376 
377  //Check current time
378  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
379  {
380  //The address is already in use?
381  if(interface->ipv4Context.addrList[i].conflict)
382  {
383  //Select a new transaction identifier
384  context->transactionId = netGenerateRand();
385 
386  //If the client detects that the address is already in use, the
387  //client must send a DHCPDECLINE message to the server and
388  //restarts the configuration process
389  dhcpClientSendDecline(context);
390 
391  //The client should wait a minimum of ten seconds before
392  //restarting the configuration process to avoid excessive
393  //network traffic in case of looping
395  }
396  //Probing is on-going?
397  else if(context->retransmitCount < DHCP_CLIENT_PROBE_NUM)
398  {
399  //Conflict detection is done using ARP probes
400  arpSendProbe(interface, interface->ipv4Context.addrList[i].addr);
401 
402  //Save the time at which the packet was sent
403  context->timestamp = time;
404  //Delay until repeated probe
405  context->timeout = DHCP_CLIENT_PROBE_DELAY;
406  //Increment retransmission counter
407  context->retransmitCount++;
408  }
409  //Probing is complete?
410  else
411  {
412  //The use of the IPv4 address is now unrestricted
413  interface->ipv4Context.addrList[i].state = IPV4_ADDR_STATE_VALID;
414 
415  //The client transitions to the ANNOUNCING state
417  }
418  }
419 }
420 
421 
422 /**
423  * @brief ANNOUNCING state
424  *
425  * The client announces its new IP address
426  *
427  * @param[in] context Pointer to the DHCP client context
428  **/
429 
431 {
432  uint_t i;
433  systime_t time;
434  NetInterface *interface;
435 
436  //Point to the underlying network interface
437  interface = context->settings.interface;
438  //Index of the IP address in the list of addresses assigned to the interface
439  i = context->settings.ipAddrIndex;
440 
441  //Get current time
442  time = osGetSystemTime();
443 
444  //Check current time
445  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
446  {
447  //Announcement is on-going?
448  if(context->retransmitCount < DHCP_CLIENT_ANNOUNCE_NUM)
449  {
450  //An ARP announcement is identical to an ARP probe, except that now
451  //the sender and target IP addresses are both set to the host's newly
452  //selected IPv4 address
453  arpSendRequest(interface, interface->ipv4Context.addrList[i].addr,
455 
456  //Save the time at which the packet was sent
457  context->timestamp = time;
458  //Delay until repeated probe
459  context->timeout = DHCP_CLIENT_ANNOUNCE_INTERVAL;
460  //Increment retransmission counter
461  context->retransmitCount++;
462  }
463 
464  //Announcing is complete?
465  if(context->retransmitCount >= DHCP_CLIENT_ANNOUNCE_NUM)
466  {
467 #if (MDNS_RESPONDER_SUPPORT == ENABLED)
468  //Restart mDNS probing process
469  mdnsResponderStartProbing(interface->mdnsResponderContext);
470 #endif
471  //Dump current DHCP configuration for debugging purpose
472  dhcpClientDumpConfig(context);
473 
474  //The client transitions to the BOUND state
476  }
477  }
478 }
479 
480 
481 /**
482  * @brief BOUND state
483  *
484  * Client has a valid lease and is in its normal operating state
485  *
486  * @param[in] context Pointer to the DHCP client context
487  **/
488 
490 {
491  systime_t t1;
492  systime_t time;
493 
494  //Get current time
495  time = osGetSystemTime();
496 
497  //A client will never attempt to extend the lifetime of the address when
498  //T1 set to 0xFFFFFFFF
499  if(context->t1 != DHCP_INFINITE_TIME)
500  {
501  //Convert T1 to milliseconds
502  if(context->t1 < (MAX_DELAY / 1000))
503  {
504  t1 = context->t1 * 1000;
505  }
506  else
507  {
508  t1 = MAX_DELAY;
509  }
510 
511  //Check the time elapsed since the lease was obtained
512  if(timeCompare(time, context->leaseStartTime + t1) >= 0)
513  {
514  //Record the time at which the client started the address renewal
515  //process
516  context->configStartTime = time;
517 
518  //Enter the RENEWING state
520  }
521  }
522 }
523 
524 
525 /**
526  * @brief RENEWING state
527  *
528  * Client is trying to renew its lease. It regularly sends DHCPREQUEST
529  * messages with the server that gave it its current lease specified, and
530  * waits for a reply
531  *
532  * @param[in] context Pointer to the DHCP client context
533  **/
534 
536 {
537  systime_t t2;
538  systime_t time;
539 
540  //Get current time
541  time = osGetSystemTime();
542 
543  //Check current time
544  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
545  {
546  //Convert T2 to milliseconds
547  if(context->t2 < (MAX_DELAY / 1000))
548  {
549  t2 = context->t2 * 1000;
550  }
551  else
552  {
553  t2 = MAX_DELAY;
554  }
555 
556  //Check whether T2 timer has expired
557  if(timeCompare(time, context->leaseStartTime + t2) < 0)
558  {
559  //First DHCPREQUEST message?
560  if(context->retransmitCount == 0)
561  {
562  //A transaction identifier is used by the client to match incoming
563  //DHCP messages with pending requests
564  context->transactionId = netGenerateRand();
565  }
566 
567  //Send a DHCPREQUEST message
568  dhcpClientSendRequest(context);
569 
570  //Save the time at which the message was sent
571  context->timestamp = time;
572 
573  //Compute the remaining time until T2 expires
574  context->timeout = context->leaseStartTime + t2 - time;
575 
576  //The client should wait one-half of the remaining time until T2, down to
577  //a minimum of 60 seconds, before retransmitting the DHCPREQUEST message
578  if(context->timeout > (2 * DHCP_CLIENT_REQUEST_MIN_DELAY))
579  {
580  context->timeout /= 2;
581  }
582 
583  //Increment retransmission counter
584  context->retransmitCount++;
585  }
586  else
587  {
588  //If no DHCPACK arrives before time T2, the client moves to REBINDING
590  }
591  }
592 }
593 
594 
595 /**
596  * @brief REBINDING state
597  *
598  * The client has failed to renew its lease with the server that originally
599  * granted it, and now seeks a lease extension with any server that can hear
600  * it. It periodically sends DHCPREQUEST messages with no server specified
601  * until it gets a reply or the lease ends
602  *
603  * @param[in] context Pointer to the DHCP client context
604  **/
605 
607 {
608  systime_t time;
609  systime_t leaseTime;
610 #if (MDNS_RESPONDER_SUPPORT == ENABLED)
611  NetInterface *interface;
612 
613  //Point to the underlying network interface
614  interface = context->settings.interface;
615 #endif
616 
617  //Get current time
618  time = osGetSystemTime();
619 
620  //Check current time
621  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
622  {
623  //Convert the lease time to milliseconds
624  if(context->leaseTime < (MAX_DELAY / 1000))
625  {
626  leaseTime = context->leaseTime * 1000;
627  }
628  else
629  {
630  leaseTime = MAX_DELAY;
631  }
632 
633  //Check whether the lease has expired
634  if(timeCompare(time, context->leaseStartTime + leaseTime) < 0)
635  {
636  //First DHCPREQUEST message?
637  if(context->retransmitCount == 0)
638  {
639  //A transaction identifier is used by the client to match incoming
640  //DHCP messages with pending requests
641  context->transactionId = netGenerateRand();
642  }
643 
644  //Send a DHCPREQUEST message
645  dhcpClientSendRequest(context);
646 
647  //Save the time at which the message was sent
648  context->timestamp = time;
649 
650  //Compute the remaining time until the lease expires
651  context->timeout = context->leaseStartTime + leaseTime - time;
652 
653  //The client should wait one-half of the remaining lease time, down to a
654  //minimum of 60 seconds, before retransmitting the DHCPREQUEST message
655  if(context->timeout > (2 * DHCP_CLIENT_REQUEST_MIN_DELAY))
656  {
657  context->timeout /= 2;
658  }
659 
660  //Increment retransmission counter
661  context->retransmitCount++;
662  }
663  else
664  {
665  //The host address is no longer valid
666  dhcpClientResetConfig(context);
667 
668 #if (MDNS_RESPONDER_SUPPORT == ENABLED)
669  //Restart mDNS probing process
670  mdnsResponderStartProbing(interface->mdnsResponderContext);
671 #endif
672  //If the lease expires before the client receives a DHCPACK, the client
673  //moves to INIT state
675  }
676  }
677 }
678 
679 #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.
#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
uint32_t netGenerateRand(void)
Generate a random 32-bit value.
Definition: net_misc.c:922
#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.
uint32_t netGenerateRandRange(uint32_t min, uint32_t max)
Generate a random value in the specified range.
Definition: net_misc.c:948
void dhcpClientStateRenewing(DhcpClientContext *context)
RENEWING state.
#define NetInterface
Definition: net.h:36
#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
@ 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:877
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:204
#define MAX_DELAY
Definition: os_port.h:77
unsigned int uint_t
Definition: compiler_port.h:50
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:817
const MacAddr MAC_BROADCAST_ADDR
Definition: ethernet.c:55
mDNS responder (Multicast DNS)
#define DHCP_CLIENT_RAND_FACTOR
Definition: dhcp_client.h:133
systime_t osGetSystemTime(void)
Retrieve system time.