From de76dfd1178cb7d77cd72b31fd2078a3562b6d22 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 Feb 2026 14:02:21 -0600 Subject: [PATCH] [wifi] Fix ESP8266 DHCP state corruption from premature dhcp_renew() wifi_apply_hostname_() calls dhcp_renew() on all interfaces with DHCP data, including when WiFi is not yet connected. lwIP's dhcp_renew() unconditionally sets the DHCP state to RENEWING (line 1159 in dhcp.c) before attempting to send, and never rolls back the state on failure. This corrupts the DHCP state machine: when WiFi later connects and dhcp_network_changed() is called, it sees RENEWING state and calls dhcp_reboot() instead of dhcp_discover(). dhcp_reboot() sends a broadcast DHCP REQUEST for IP 0.0.0.0 (since no lease was ever obtained), which can put some routers into a persistent bad state that requires a router restart to clear. This bug has existed since commit 072b2c445c (Dec 2019, "Add ESP8266 core v2.6.2") and affects every ESP8266 WiFi connection attempt. Most routers handle the bogus DHCP REQUEST gracefully (NAK then fallback to DISCOVER), but affected routers get stuck and refuse connections from the device until restarted. Fix: guard the dhcp_renew() call with netif_is_link_up() so it only runs when the interface actually has an active link. The hostname is still set on the netif regardless, so it will be included in DHCP packets when the connection is established normally. Co-Authored-By: Claude Opus 4.6 --- esphome/components/wifi/wifi_component_esp8266.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c87345f0bf..b913ca5f70 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -224,8 +224,14 @@ bool WiFiComponent::wifi_apply_hostname_() { #else intf->hostname = wifi_station_get_hostname(); #endif - if (netif_dhcp_data(intf) != nullptr) { - // renew already started DHCP leases + if (netif_dhcp_data(intf) != nullptr && netif_is_link_up(intf)) { + // Renew already started DHCP leases to inform server of hostname change. + // Only attempt when the interface has link — calling dhcp_renew() without + // an active connection corrupts lwIP's DHCP state machine (it unconditionally + // sets state to RENEWING before attempting to send, and never rolls back on + // failure). This causes dhcp_network_changed() to call dhcp_reboot() instead + // of dhcp_discover() when WiFi later connects, sending a bogus DHCP REQUEST + // for IP 0.0.0.0 that can put some routers into a persistent bad state. err_t lwipret = dhcp_renew(intf); if (lwipret != ERR_OK) { ESP_LOGW(TAG, "wifi_apply_hostname_(%s): lwIP error %d on interface %c%c (index %d)", intf->hostname,