From 2cafada721d85e8904a22fdcb467a0601d7f9fac Mon Sep 17 00:00:00 2001 From: Colin McInnes Date: Tue, 1 Sep 2026 17:05:13 -0600 Subject: [PATCH 1/3] Add exit-hook reasons for missing server responses --- hooks/dhcpcd-run-hooks.8.in | 24 ++++++++++++++++++++++++ src/dhcp.c | 12 +++++++++++- src/dhcp6.c | 18 ++++++++++++------ src/ipv6nd.c | 4 +++- src/script.c | 23 ++++++++++++++++++++++- 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/hooks/dhcpcd-run-hooks.8.in b/hooks/dhcpcd-run-hooks.8.in index 8672a22f..12c091a4 100644 --- a/hooks/dhcpcd-run-hooks.8.in +++ b/hooks/dhcpcd-run-hooks.8.in @@ -124,6 +124,30 @@ dhcpcd's lease or state expired and it failed to obtain a new one. .It Dv NAK dhcpcd received a NAK from the DHCP server. This should be treated as EXPIRE. +.It Dv DISC_NO_OFFER +dhcpcd retransmitted a DHCP DISCOVER because no OFFER was received. +This is a notification only and does not configure or deconfigure. +.It Dv SOLICIT_NO_ADVERT +dhcpcd retransmitted a DHCPv6 Solicit because no Advertise was received. +This is a notification only and does not configure or deconfigure. +.It Dv REQ_NO_RESP +dhcpcd failed to receive a response to a DHCP REQUEST and is falling back +to DISCOVER. +This is a notification only and does not configure or deconfigure. +.It Dv REQ6_NO_REPLY +dhcpcd failed to receive a Reply to a DHCPv6 Request and is falling back +to Solicit. +This is a notification only and does not configure or deconfigure. +.It Dv RENEW_NO_RESP +dhcpcd failed to renew its DHCPv4 lease at T2 and is entering REBIND. +This is a notification only and does not configure or deconfigure. +.It Dv RENEW6_NO_RESP +dhcpcd failed to renew its DHCPv6 lease at T2 and is entering REBIND. +This is a notification only and does not configure or deconfigure. +.It Dv RS_TIMEOUT_NO_RA +dhcpcd sent the maximum number of Router Solicitations without receiving +a Router Advertisement. +This is a notification only and does not configure or deconfigure. .It Dv RECONFIGURE dhcpcd has been instructed to reconfigure an interface. .It Dv ROUTERADVERT diff --git a/src/dhcp.c b/src/dhcp.c index 078f6927..ffbb754c 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1944,7 +1944,15 @@ send_inform(void *arg) static void send_discover(void *arg) { - send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + /* First call: interval==0 (set to 4 inside send_message). + * Retransmits: interval already 4/8/... - mirror ISC's dhclient. */ + int retransmit = state->interval != 0; + + send_message(ifp, DHCP_DISCOVER, send_discover); + if (retransmit) + script_runreason(ifp, "DISC_NO_OFFER"); } static void @@ -2005,6 +2013,7 @@ dhcp_requestfailed(void *arg) state->offer = NULL; state->offer_len = 0; state->interval = 0; + script_runreason(ifp, "REQ_NO_RESP"); dhcp_discover(ifp); } @@ -2096,6 +2105,7 @@ dhcp_rebind(void *arg) struct dhcp_lease *lease = &state->lease; logwarnx("%s: failed to renew DHCP, rebinding", ifp->name); + script_runreason(ifp, "RENEW_NO_RESP"); logdebugx("%s: expire in %" PRIu32 " seconds", ifp->name, lease->leasetime - lease->rebindtime); state->state = DHS_REBIND; diff --git a/src/dhcp6.c b/src/dhcp6.c index 07ffeb50..21692346 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -1481,7 +1481,10 @@ dhcp6_sendinform(void *arg) static void dhcp6_senddiscover2(void *arg) { - dhcp6_sendmessage(arg, dhcp6_senddiscover2); + struct interface *ifp = arg; + + dhcp6_sendmessage(ifp, dhcp6_senddiscover2); + script_runreason(ifp, "SOLICIT_NO_ADVERT"); } static void @@ -1495,9 +1498,10 @@ dhcp6_senddiscover1(void *arg) struct interface *ifp = arg; struct dhcp6_state *state = D6_STATE(ifp); - if (state->recv == NULL || state->recv->type != DHCP6_ADVERTISE) - dhcp6_sendmessage(arg, dhcp6_senddiscover2); - else + if (state->recv == NULL || state->recv->type != DHCP6_ADVERTISE) { + dhcp6_sendmessage(ifp, dhcp6_senddiscover2); + script_runreason(ifp, "SOLICIT_NO_ADVERT"); + } else dhcp6_startrequest(ifp); } @@ -1863,6 +1867,7 @@ dhcp6_failrequest(void *arg) int llevel = dhcp6_failloglevel(ifp); logmessage(llevel, "%s: failed to request DHCPv6 address", ifp->name); + script_runreason(ifp, "REQ6_NO_REPLY"); dhcp6_fail(ifp, true); } @@ -1927,9 +1932,10 @@ dhcp6_startrebind(void *arg) state->RTC = 0; state->MRC = 0; - if (state->state == DH6S_RENEW) + if (state->state == DH6S_RENEW) { logwarnx("%s: failed to renew DHCPv6, rebinding", ifp->name); - else { + script_runreason(ifp, "RENEW6_NO_RESP"); + } else { loginfox("%s: rebinding prior DHCPv6 lease", ifp->name); #ifndef SMALL diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 0004b1b5..4cde6244 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -434,8 +434,10 @@ ipv6nd_sendrsprobe(void *arg) if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) eloop_timeout_add_sec(ifp->ctx->eloop, RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); - else + else { logwarnx("%s: no IPv6 Routers available", ifp->name); + script_runreason(ifp, "RS_TIMEOUT_NO_RA"); + } } static void diff --git a/src/script.c b/src/script.c index e059472e..30620ab0 100644 --- a/src/script.c +++ b/src/script.c @@ -325,7 +325,21 @@ make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, protocol = PROTO_DHCP; #endif } +#ifdef INET + else if (strcmp(reason, "DISC_NO_OFFER") == 0 || + strcmp(reason, "REQ_NO_RESP") == 0 || + strcmp(reason, "RENEW_NO_RESP") == 0) + protocol = PROTO_DHCP; +#endif #ifdef INET6 +#ifdef DHCP6 + else if (strcmp(reason, "SOLICIT_NO_ADVERT") == 0 || + strcmp(reason, "REQ6_NO_REPLY") == 0 || + strcmp(reason, "RENEW6_NO_RESP") == 0) + protocol = PROTO_DHCP6; +#endif + else if (strcmp(reason, "RS_TIMEOUT_NO_RA") == 0) + protocol = PROTO_RA; else if (strcmp(reason, "STATIC6") == 0) protocol = PROTO_STATIC6; #ifdef DHCP6 @@ -440,7 +454,14 @@ make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, if_down = ifo->options & DHCPCD_RELEASE ? true_str : false_str; } else if (strcmp(reason, "TEST") == 0 || strcmp(reason, "PREINIT") == 0 || strcmp(reason, "CARRIER") == 0 || - strcmp(reason, "STOP") == 0 || strcmp(reason, "UNKNOWN") == 0) { + strcmp(reason, "STOP") == 0 || strcmp(reason, "UNKNOWN") == 0 || + strcmp(reason, "DISC_NO_OFFER") == 0 || + strcmp(reason, "REQ_NO_RESP") == 0 || + strcmp(reason, "RENEW_NO_RESP") == 0 || + strcmp(reason, "SOLICIT_NO_ADVERT") == 0 || + strcmp(reason, "REQ6_NO_REPLY") == 0 || + strcmp(reason, "RENEW6_NO_RESP") == 0 || + strcmp(reason, "RS_TIMEOUT_NO_RA") == 0) { if_up = false_str; if_down = false_str; } else if (strcmp(reason, "NOCARRIER") == 0) { From 00fa7d01b6bf0a0b2e13e70aa829a7521a18fa4e Mon Sep 17 00:00:00 2001 From: Colin McInnes Date: Tue, 1 Sep 2026 17:18:15 -0600 Subject: [PATCH 2/3] Gate DISC_NO_OFFER on successful transmission --- src/dhcp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dhcp.c b/src/dhcp.c index ffbb754c..3895de46 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1950,8 +1950,7 @@ send_discover(void *arg) * Retransmits: interval already 4/8/... - mirror ISC's dhclient. */ int retransmit = state->interval != 0; - send_message(ifp, DHCP_DISCOVER, send_discover); - if (retransmit) + if (send_message(ifp, DHCP_DISCOVER, send_discover) && retransmit) script_runreason(ifp, "DISC_NO_OFFER"); } From d81c743b6eacab156ed3e1e39be076d37ca22a8b Mon Sep 17 00:00:00 2001 From: Colin McInnes Date: Tue, 1 Sep 2026 20:43:28 -0600 Subject: [PATCH 3/3] send_message doesn't have a return value checking if send_message put something in the pipeline is a good idea. but it doesn't have one yet. --- src/dhcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dhcp.c b/src/dhcp.c index 3895de46..ffbb754c 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1950,7 +1950,8 @@ send_discover(void *arg) * Retransmits: interval already 4/8/... - mirror ISC's dhclient. */ int retransmit = state->interval != 0; - if (send_message(ifp, DHCP_DISCOVER, send_discover) && retransmit) + send_message(ifp, DHCP_DISCOVER, send_discover); + if (retransmit) script_runreason(ifp, "DISC_NO_OFFER"); }