[PATCH v2] dhcp: fix overflow causing retries to stop

James Prestwood <[email protected]> Thu, 11 Jan 2024 05:33:31 -0800
Newsgroups dev.linux.lists.ell
Message-ID <[email protected]>
If DHCP is in a SELECTING/REQUESTING state and the number of attempts
reached a value where 2 << attempts overflowed an unsigned int the
next timeout would become zero, causing DHCP to never retry without
any event notification.

Roughly 2 minutes has passed when we reach >5 attempts so it makes
little sense to retry indefinitely, at least without notifying the
upper layers (which could decided to retry themselves).

Add a new event L_DHCP_CLIENT_EVENT_MAX_RETRIES_REACHED to notify
netconfig of the situation. Netconfig will then send a failure event
and the consumer can decide how to proceed.

Fixes: f130c448 ("dhcp: Introduce timeout fuzzing")
---
 ell/dhcp.c      | 19 ++++++++++++++++---
 ell/dhcp.h      |  1 +
 ell/netconfig.c |  6 ++++++
 3 files changed, 23 insertions(+), 3 deletions(-)

v2:
 * Added new event instead of retry forever.

diff --git a/ell/dhcp.c b/ell/dhcp.c
index ece3e23..79fc54c 100644
--- a/ell/dhcp.c
+++ b/ell/dhcp.c
@@ -557,10 +557,23 @@ static void dhcp_client_timeout_resend(struct l_timeout *timeout,
 		 * RFC 2131 Section 4.1:
 		 * "The retransmission delay SHOULD be doubled with subsequent
 		 * retransmissions up to a maximum of 64 seconds.
+		 *
+		 * The maximum is hit after 5 attempts (2 << 5 == 64)
 		 */
-		client->attempt += 1;
-		next_timeout = minsize(2 << client->attempt, 64);
-		break;
+		if (client->attempt <= 5) {
+			next_timeout = 2 << client->attempt++;
+			break;
+		}
+
+		/*
+		 * DHCP server is non-responsive after ~2 minutes, relay this to
+		 * upper layers do decide how to proceed
+		 */
+		CLIENT_DEBUG("Max request/discover retires reached");
+
+		dhcp_client_event_notify(client,
+				L_DHCP_CLIENT_EVENT_MAX_RETRIES_REACHED);
+		return;
 	case DHCP_STATE_INIT:
 	case DHCP_STATE_INIT_REBOOT:
 	case DHCP_STATE_REBOOTING:
diff --git a/ell/dhcp.h b/ell/dhcp.h
index 6ce4dde..4db573d 100644
--- a/ell/dhcp.h
+++ b/ell/dhcp.h
@@ -41,6 +41,7 @@ enum l_dhcp_client_event {
 	L_DHCP_CLIENT_EVENT_LEASE_EXPIRED,
 	L_DHCP_CLIENT_EVENT_LEASE_RENEWED,
 	L_DHCP_CLIENT_EVENT_NO_LEASE,
+	L_DHCP_CLIENT_EVENT_MAX_RETRIES_REACHED,
 };
 
 enum l_dhcp_server_event {
diff --git a/ell/netconfig.c b/ell/netconfig.c
index ab59299..1e6912a 100644
--- a/ell/netconfig.c
+++ b/ell/netconfig.c
@@ -548,6 +548,12 @@ static void netconfig_dhcp_event_handler(struct l_dhcp_client *client,
 		if (!l_dhcp_client_start(nc->dhcp_client))
 			netconfig_failed(nc, AF_INET);
 
+		break;
+	case L_DHCP_CLIENT_EVENT_MAX_RETRIES_REACHED:
+		L_WARN_ON(nc->v4_configured);
+
+		netconfig_failed(nc, AF_INET);
+
 		break;
 	}
 }
-- 
2.34.1