[dhcp] Handle DHCPNAK by returning to discovery state

Handle a DHCPNAK by returning to the discovery state to allow iPXE to
attempt to obtain a replacement IPv4 address.

Reuse the existing logic for deferring discovery when the link is
blocked: this avoids hammering a misconfigured DHCP server with a
non-stop stream of requests and allows the DHCP process to eventually
time out and fail.

Originally-implemented-by: Blake Rouse <blake.rouse@canonical.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c
index 4ebd38b..a335a77 100644
--- a/src/net/udp/dhcp.c
+++ b/src/net/udp/dhcp.c
@@ -444,6 +444,26 @@
 }
 
 /**
+ * Defer DHCP discovery
+ *
+ * @v dhcp		DHCP session
+ */
+static void dhcp_defer ( struct dhcp_session *dhcp ) {
+
+	/* Do nothing if we have reached the deferral limit */
+	if ( dhcp->count > DHCP_DISC_MAX_DEFERRALS )
+		return;
+
+	/* Return to discovery state */
+	DBGC ( dhcp, "DHCP %p deferring discovery\n", dhcp );
+	dhcp_set_state ( dhcp, &dhcp_state_discover );
+
+	/* Delay first DHCPDISCOVER */
+	start_timer_fixed ( &dhcp->timer,
+			    ( DHCP_DISC_START_TIMEOUT_SEC * TICKS_PER_SEC ) );
+}
+
+/**
  * Handle timer expiry during DHCP discovery
  *
  * @v dhcp		DHCP session
@@ -462,14 +482,8 @@
 	dhcp_tx ( dhcp );
 
 	/* If link is blocked, defer DHCP discovery timeout */
-	if ( netdev_link_blocked ( dhcp->netdev ) &&
-	     ( dhcp->count <= DHCP_DISC_MAX_DEFERRALS ) ) {
-		DBGC ( dhcp, "DHCP %p deferring discovery timeout\n", dhcp );
-		dhcp->start = currticks();
-		start_timer_fixed ( &dhcp->timer,
-				    ( DHCP_DISC_START_TIMEOUT_SEC *
-				      TICKS_PER_SEC ) );
-	}
+	if ( netdev_link_blocked ( dhcp->netdev ) )
+	     dhcp_defer ( dhcp );
 }
 
 /** DHCP discovery state operations */
@@ -553,9 +567,17 @@
 		DBGC ( dhcp, " for %s", inet_ntoa ( ip ) );
 	DBGC ( dhcp, "\n" );
 
-	/* Filter out unacceptable responses */
+	/* Filter out invalid port */
 	if ( peer->sin_port != htons ( BOOTPS_PORT ) )
 		return;
+
+	/* Handle DHCPNAK */
+	if ( msgtype == DHCPNAK ) {
+		dhcp_defer ( dhcp );
+		return;
+	}
+
+	/* Filter out unacceptable responses */
 	if ( msgtype /* BOOTP */ && ( msgtype != DHCPACK ) )
 		return;
 	if ( server_id.s_addr != dhcp->server.s_addr )