Skip to content

Commit e707cf2

Browse files
rlubosanangl
authored andcommitted
[nrf fromtree] net: dhcpv4: Reimplement RENEW/REBIND logic according to RFC2131
DHCP Request retransmission in RENEW and REBIND states was not compliant with RFC2131. The retransmission interval should not be calculated as in REQUESTING state in such case, but rather calculated based on the remaining T2 or lease time (depending on current state). RFC doesn't also mention any retransmission count limit for those states. The client should retransmit the REQUEST until T2 or lease expiry time are reached. Signed-off-by: Robert Lubos <[email protected]> (cherry picked from commit 4d4391b)
1 parent 3d2f1c7 commit e707cf2

File tree

2 files changed

+174
-93
lines changed

2 files changed

+174
-93
lines changed

subsys/net/lib/dhcpv4/dhcpv4.c

Lines changed: 168 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,64 @@ static void dhcpv4_set_timeout(struct net_if_dhcpv4 *dhcpv4,
394394
k_work_reschedule(&timeout_work, K_NO_WAIT);
395395
}
396396

397+
/* Set a new timeout w/o updating base time. Used for RENEWING and REBINDING to
398+
* keep track of T1/T2/lease timeouts.
399+
*/
400+
static void dhcpv4_set_timeout_inc(struct net_if_dhcpv4 *dhcpv4,
401+
int64_t now, uint32_t timeout)
402+
{
403+
int64_t timeout_ms;
404+
405+
NET_DBG("sched timeout dhcvp4=%p timeout=%us", dhcpv4, timeout);
406+
407+
timeout_ms = (now - dhcpv4->timer_start) + MSEC_PER_SEC * timeout;
408+
dhcpv4->request_time = (uint32_t)(timeout_ms / MSEC_PER_SEC);
409+
}
410+
411+
static uint32_t dhcpv4_get_timeleft(int64_t start, uint32_t time, int64_t now)
412+
{
413+
int64_t deadline = start + MSEC_PER_SEC * time;
414+
uint32_t ret = 0U;
415+
416+
/* If we haven't reached the deadline, calculate the
417+
* rounded-up whole seconds until the deadline.
418+
*/
419+
if (deadline > now) {
420+
ret = (uint32_t)DIV_ROUND_UP(deadline - now, MSEC_PER_SEC);
421+
}
422+
423+
return ret;
424+
}
425+
426+
static uint32_t dhcpv4_request_timeleft(struct net_if *iface, int64_t now)
427+
{
428+
uint32_t request_time = iface->config.dhcpv4.request_time;
429+
430+
return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
431+
request_time, now);
432+
}
433+
434+
static uint32_t dhcpv4_renewal_timeleft(struct net_if *iface, int64_t now)
435+
{
436+
return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
437+
iface->config.dhcpv4.renewal_time,
438+
now);
439+
}
440+
441+
static uint32_t dhcpv4_rebinding_timeleft(struct net_if *iface, int64_t now)
442+
{
443+
return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
444+
iface->config.dhcpv4.rebinding_time,
445+
now);
446+
}
447+
448+
static uint32_t dhcpv4_lease_timeleft(struct net_if *iface, int64_t now)
449+
{
450+
return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
451+
iface->config.dhcpv4.lease_time,
452+
now);
453+
}
454+
397455
/* Must be invoked with lock held */
398456
static uint32_t dhcpv4_update_message_timeout(struct net_if_dhcpv4 *dhcpv4)
399457
{
@@ -415,6 +473,59 @@ static uint32_t dhcpv4_update_message_timeout(struct net_if_dhcpv4 *dhcpv4)
415473
return timeout;
416474
}
417475

476+
static uint32_t dhcpv4_calculate_renew_rebind_timeout(uint32_t timeleft)
477+
{
478+
uint32_t timeout;
479+
480+
/* RFC2131 4.4.5:
481+
* In both RENEWING and REBINDING states, if the client receives no
482+
* response to its DHCPREQUEST message, the client SHOULD wait one-half
483+
* of the remaining time until T2 (in RENEWING state) and one-half of
484+
* the remaining lease time (in REBINDING state), down to a minimum of
485+
* 60 seconds, before retransmitting the DHCPREQUEST message.
486+
*/
487+
488+
if (timeleft < DHCPV4_RENEW_REBIND_TIMEOUT_MIN) {
489+
timeout = timeleft;
490+
} else if (timeleft / 2U < DHCPV4_RENEW_REBIND_TIMEOUT_MIN) {
491+
timeout = DHCPV4_RENEW_REBIND_TIMEOUT_MIN;
492+
} else {
493+
timeout = timeleft / 2U;
494+
}
495+
496+
return timeout;
497+
}
498+
499+
static uint32_t dhcpv4_update_renew_timeout(struct net_if *iface)
500+
{
501+
struct net_if_dhcpv4 *dhcpv4 = &iface->config.dhcpv4;
502+
int64_t now = k_uptime_get();
503+
uint32_t timeout;
504+
505+
timeout = dhcpv4_calculate_renew_rebind_timeout(
506+
dhcpv4_rebinding_timeleft(iface, now));
507+
508+
dhcpv4->attempts++;
509+
dhcpv4_set_timeout_inc(dhcpv4, now, timeout);
510+
511+
return timeout;
512+
}
513+
514+
static uint32_t dhcpv4_update_rebind_timeout(struct net_if *iface)
515+
{
516+
struct net_if_dhcpv4 *dhcpv4 = &iface->config.dhcpv4;
517+
int64_t now = k_uptime_get();
518+
uint32_t timeout;
519+
520+
timeout = dhcpv4_calculate_renew_rebind_timeout(
521+
dhcpv4_lease_timeleft(iface, now));
522+
523+
dhcpv4->attempts++;
524+
dhcpv4_set_timeout_inc(dhcpv4, now, timeout);
525+
526+
return timeout;
527+
}
528+
418529
/* Prepare DHCPv4 Message request and send it to peer.
419530
*
420531
* Returns the number of seconds until the next time-triggered event,
@@ -447,6 +558,7 @@ static uint32_t dhcpv4_send_request(struct net_if *iface)
447558
with_requested_ip = true;
448559
memcpy(&iface->config.dhcpv4.request_server_addr, &iface->config.dhcpv4.server_id,
449560
sizeof(struct in_addr));
561+
timeout = dhcpv4_update_message_timeout(&iface->config.dhcpv4);
450562
break;
451563
case NET_DHCPV4_RENEWING:
452564
/* Since we have an address populate the ciaddr field.
@@ -456,6 +568,7 @@ static uint32_t dhcpv4_send_request(struct net_if *iface)
456568
/* UNICAST the DHCPREQUEST */
457569
src_addr = ciaddr;
458570
server_addr = &iface->config.dhcpv4.server_id;
571+
timeout = dhcpv4_update_renew_timeout(iface);
459572

460573
/* RFC2131 4.4.5 Client MUST NOT include server
461574
* identifier in the DHCPREQUEST.
@@ -466,12 +579,11 @@ static uint32_t dhcpv4_send_request(struct net_if *iface)
466579
*/
467580
ciaddr = &iface->config.dhcpv4.requested_ip;
468581
src_addr = ciaddr;
582+
timeout = dhcpv4_update_rebind_timeout(iface);
469583

470584
break;
471585
}
472586

473-
timeout = dhcpv4_update_message_timeout(&iface->config.dhcpv4);
474-
475587
pkt = dhcpv4_create_message(iface, NET_DHCPV4_MSG_TYPE_REQUEST,
476588
ciaddr, src_addr, server_addr,
477589
with_server_id, with_requested_ip);
@@ -559,60 +671,6 @@ static void dhcpv4_enter_selecting(struct net_if *iface)
559671
net_dhcpv4_state_name(iface->config.dhcpv4.state));
560672
}
561673

562-
static uint32_t dhcpv4_get_timeleft(int64_t start, uint32_t time, int64_t now)
563-
{
564-
int64_t deadline = start + MSEC_PER_SEC * time;
565-
uint32_t ret = 0U;
566-
567-
/* If we haven't reached the deadline, calculate the
568-
* rounded-up whole seconds until the deadline.
569-
*/
570-
if (deadline > now) {
571-
ret = (uint32_t)DIV_ROUND_UP(deadline - now, MSEC_PER_SEC);
572-
}
573-
574-
return ret;
575-
}
576-
577-
static uint32_t dhcpv4_request_timeleft(struct net_if *iface, int64_t now)
578-
{
579-
uint32_t request_time = iface->config.dhcpv4.request_time;
580-
581-
return dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
582-
request_time, now);
583-
}
584-
585-
static uint32_t dhcpv4_renewal_timeleft(struct net_if *iface, int64_t now)
586-
{
587-
uint32_t rem = dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
588-
iface->config.dhcpv4.renewal_time,
589-
now);
590-
591-
if (rem == 0U) {
592-
iface->config.dhcpv4.state = NET_DHCPV4_RENEWING;
593-
NET_DBG("enter state=%s",
594-
net_dhcpv4_state_name(iface->config.dhcpv4.state));
595-
iface->config.dhcpv4.attempts = 0U;
596-
}
597-
598-
return rem;
599-
}
600-
601-
static uint32_t dhcpv4_rebinding_timeleft(struct net_if *iface, int64_t now)
602-
{
603-
uint32_t rem = dhcpv4_get_timeleft(iface->config.dhcpv4.timer_start,
604-
iface->config.dhcpv4.rebinding_time,
605-
now);
606-
if (rem == 0U) {
607-
iface->config.dhcpv4.state = NET_DHCPV4_REBINDING;
608-
NET_DBG("enter state=%s",
609-
net_dhcpv4_state_name(iface->config.dhcpv4.state));
610-
iface->config.dhcpv4.attempts = 0U;
611-
}
612-
613-
return rem;
614-
}
615-
616674
static void dhcpv4_enter_requesting(struct net_if *iface, struct dhcp_msg *msg)
617675
{
618676
iface->config.dhcpv4.attempts = 0U;
@@ -629,36 +687,60 @@ static void dhcpv4_enter_requesting(struct net_if *iface, struct dhcp_msg *msg)
629687
/* Must be invoked with lock held */
630688
static void dhcpv4_enter_bound(struct net_if *iface)
631689
{
632-
uint32_t renewal_time;
633-
uint32_t rebinding_time;
690+
struct net_if_dhcpv4 *dhcpv4 = &iface->config.dhcpv4;
634691

635-
renewal_time = iface->config.dhcpv4.renewal_time;
636-
if (!renewal_time) {
692+
/* Load defaults in case server did not provide T1/T2 values. */
693+
if (dhcpv4->renewal_time == 0U) {
637694
/* The default renewal time rfc2131 4.4.5 */
638-
renewal_time = iface->config.dhcpv4.lease_time / 2U;
639-
iface->config.dhcpv4.renewal_time = renewal_time;
695+
dhcpv4->renewal_time = dhcpv4->lease_time / 2U;
640696
}
641697

642-
rebinding_time = iface->config.dhcpv4.rebinding_time;
643-
if (!rebinding_time) {
698+
if (dhcpv4->rebinding_time == 0U) {
644699
/* The default rebinding time rfc2131 4.4.5 */
645-
rebinding_time = iface->config.dhcpv4.lease_time * 875U / 1000;
646-
iface->config.dhcpv4.rebinding_time = rebinding_time;
700+
dhcpv4->rebinding_time = dhcpv4->lease_time * 875U / 1000U;
701+
}
702+
703+
/* RFC2131 4.4.5:
704+
* T1 MUST be earlier than T2, which, in turn, MUST be earlier than the
705+
* time at which the client's lease will expire.
706+
*/
707+
if ((dhcpv4->renewal_time >= dhcpv4->rebinding_time) ||
708+
(dhcpv4->rebinding_time >= dhcpv4->lease_time)) {
709+
/* In case server did not provide valid values, fall back to
710+
* defaults.
711+
*/
712+
dhcpv4->renewal_time = dhcpv4->lease_time / 2U;
713+
dhcpv4->rebinding_time = dhcpv4->lease_time * 875U / 1000U;
647714
}
648715

649-
iface->config.dhcpv4.state = NET_DHCPV4_BOUND;
716+
dhcpv4->state = NET_DHCPV4_BOUND;
650717
NET_DBG("enter state=%s renewal=%us rebinding=%us",
651-
net_dhcpv4_state_name(iface->config.dhcpv4.state),
652-
renewal_time, rebinding_time);
718+
net_dhcpv4_state_name(dhcpv4->state),
719+
dhcpv4->renewal_time, dhcpv4->rebinding_time);
653720

654-
dhcpv4_set_timeout(&iface->config.dhcpv4,
655-
MIN(renewal_time, rebinding_time));
721+
dhcpv4_set_timeout(dhcpv4, dhcpv4->renewal_time);
656722

657723
net_mgmt_event_notify_with_info(NET_EVENT_IPV4_DHCP_BOUND, iface,
658724
&iface->config.dhcpv4,
659725
sizeof(iface->config.dhcpv4));
660726
}
661727

728+
static void dhcpv4_enter_renewing(struct net_if *iface)
729+
{
730+
iface->config.dhcpv4.state = NET_DHCPV4_RENEWING;
731+
iface->config.dhcpv4.attempts = 0U;
732+
NET_DBG("enter state=%s",
733+
net_dhcpv4_state_name(iface->config.dhcpv4.state));
734+
}
735+
736+
static void dhcpv4_enter_rebinding(struct net_if *iface)
737+
{
738+
iface->config.dhcpv4.state = NET_DHCPV4_REBINDING;
739+
iface->config.dhcpv4.attempts = 0U;
740+
NET_DBG("enter state=%s",
741+
net_dhcpv4_state_name(iface->config.dhcpv4.state));
742+
}
743+
662744
static uint32_t dhcpv4_manage_timers(struct net_if *iface, int64_t now)
663745
{
664746
uint32_t timeleft = dhcpv4_request_timeleft(iface, now);
@@ -701,34 +783,34 @@ static uint32_t dhcpv4_manage_timers(struct net_if *iface, int64_t now)
701783
return dhcpv4_send_request(iface);
702784
case NET_DHCPV4_BOUND:
703785
timeleft = dhcpv4_renewal_timeleft(iface, now);
704-
if (timeleft != 0U) {
705-
timeleft = MIN(timeleft,
706-
dhcpv4_rebinding_timeleft(iface, now));
707-
}
708786
if (timeleft == 0U) {
787+
dhcpv4_enter_renewing(iface);
709788
return dhcpv4_send_request(iface);
710789
}
711790

712791
return timeleft;
713792
case NET_DHCPV4_RENEWING:
714-
case NET_DHCPV4_REBINDING:
715-
if (iface->config.dhcpv4.attempts >=
716-
DHCPV4_MAX_NUMBER_OF_ATTEMPTS) {
717-
NET_DBG("too many attempts, restart");
793+
timeleft = dhcpv4_rebinding_timeleft(iface, now);
794+
if (timeleft == 0U) {
795+
dhcpv4_enter_rebinding(iface);
796+
}
718797

719-
if (!net_if_ipv4_addr_rm(iface,
720-
&iface->config.dhcpv4.requested_ip)) {
798+
return dhcpv4_send_request(iface);
799+
case NET_DHCPV4_REBINDING:
800+
timeleft = dhcpv4_lease_timeleft(iface, now);
801+
if (timeleft == 0U) {
802+
if (!net_if_ipv4_addr_rm(
803+
iface,
804+
&iface->config.dhcpv4.requested_ip)) {
721805
NET_DBG("Failed to remove addr from iface");
722806
}
723807

724-
/* Maximum number of renewal attempts failed, so start
725-
* from the beginning.
726-
*/
808+
/* Lease time expired, so start from the beginning. */
727809
dhcpv4_enter_selecting(iface);
728810
return dhcpv4_send_discover(iface);
729-
} else {
730-
return dhcpv4_send_request(iface);
731811
}
812+
813+
return dhcpv4_send_request(iface);
732814
}
733815

734816
return UINT32_MAX;

subsys/net/lib/dhcpv4/dhcpv4_internal.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,7 @@ struct dhcp_msg {
8686
DHCPV4_OLV_END_SIZE)
8787

8888

89-
/* TODO:
90-
* 1) Support T2(Rebind) timer.
91-
*/
92-
93-
/* Maximum number of REQUEST or RENEWAL retransmits before reverting
94-
* to DISCOVER.
95-
*/
89+
/* Maximum number of REQUEST retransmits before reverting to DISCOVER. */
9690
#define DHCPV4_MAX_NUMBER_OF_ATTEMPTS 3
9791

9892
/* Initial message retry timeout (s). This timeout increases
@@ -109,6 +103,11 @@ struct dhcp_msg {
109103
*/
110104
#define DHCPV4_INITIAL_DELAY_MIN 1
111105

106+
/* Minimum retransmission timeout in RENEW and REBIND states (in seconds).
107+
* RFC2131 4.4.5
108+
*/
109+
#define DHCPV4_RENEW_REBIND_TIMEOUT_MIN 60
110+
112111
#if defined(CONFIG_NET_DHCPV4)
113112

114113
int net_dhcpv4_init(void);

0 commit comments

Comments
 (0)