[wifi] Fix ESP8266 DHCP state corruption from premature dhcp_renew()#13983
[wifi] Fix ESP8266 DHCP state corruption from premature dhcp_renew()#13983
Conversation
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 072b2c4 (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 <noreply@anthropic.com>
|
To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file: external_components:
- source: github://pr#13983
components: [wifi]
refresh: 1h(Added by the PR bot) |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #13983 +/- ##
==========================================
+ Coverage 74.08% 74.11% +0.02%
==========================================
Files 55 55
Lines 11588 11588
Branches 1577 1577
==========================================
+ Hits 8585 8588 +3
+ Misses 2600 2598 -2
+ Partials 403 402 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Memory Impact AnalysisComponents:
📊 Component Memory Breakdown
🔍 Symbol-Level Changes (click to expand)Changed Symbols
This analysis runs automatically when components change. Memory usage is measured from a representative test configuration. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a 6-year-old bug in ESP8266 WiFi connection code where dhcp_renew() was called before the interface had an active link, corrupting lwIP's DHCP state machine and causing persistent connection issues with some routers.
Changes:
- Added
netif_is_link_up(intf)guard to preventdhcp_renew()from being called before WiFi connection is established - Added comprehensive comment explaining the bug, root cause in lwIP, and consequences
- Preserves hostname setting behavior while preventing state machine corruption
|
I saw this all the time but my router doesn't care about the This fixes it. I spent an hour looking for it and claude found it in 20 minutes on the 2nd attempt |
|
It was 100% reproducible on esp8266 if the first wifi connection fails. To reproduce, wrap in foil on the first attempt and unwrap. |
netif_is_link_up() is insufficient — if wifi_station_connect() completes quickly (e.g. fast_connect), the setup() call at line 710 could reach dhcp_renew() with link up but DHCP still in SELECTING or REQUESTING state, causing the same state corruption. Check dhcp->state == DHCP_STATE_BOUND directly to ensure dhcp_renew() is only called when there is an actual lease to renew. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DHCP_STATE_BOUND alone is insufficient — during reconnection, DHCP can remain BOUND from a previous connection while the link is down (wifi_disconnect_() doesn't stop DHCP). Both conditions are needed: DHCP must be BOUND and the interface must have link. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
likely original code modeled after esp8266/Arduino#6680 |
The dhcp_renew() loop was cargo-culted from Arduino ESP8266's WiFi.hostname() which was designed for changing the hostname on an already-connected system with multiple interfaces (WiFi+Ethernet). In ESPHome, wifi_apply_hostname_() is only called from: - setup_() — before WiFi connects (link never up) - wifi_sta_connect_() — after wifi_disconnect_() (link always down) The hostname is fixed at compile time and never changes at runtime. Setting intf->hostname is sufficient — lwIP automatically includes it in DHCP DISCOVER/REQUEST packets via LWIP_NETIF_HOSTNAME. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cannot confirm wifi_station_disconnect() synchronously clears the lwIP netif LINK_UP flag on ESP8266 NONOS SDK. The comment doesn't need to make claims about link state since the fix is simply that the hostname never changes at runtime, making dhcp_renew() pointless. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
thanks |

What does this implement/fix?
Remove a cargo-culted
dhcp_renew()call that corrupts lwIP's DHCP state machine on every ESP8266 WiFi connection attempt.Origin
The
dhcp_renew()loop inwifi_apply_hostname_()was copied from the ESP8266 Arduino core'sLwipIntf::hostname()in commit 072b2c4 (Dec 2019). That function was designed for a different use case: changing the hostname on an already-connected system with multiple interfaces (WiFi + Ethernet), where you need to inform each interface's DHCP server of the new name.Why it's unnecessary in ESPHome
The hostname is fixed at compile time and never changes at runtime. Setting
intf->hostnameis sufficient — lwIP automatically includes it in DHCP DISCOVER/REQUEST packets viaLWIP_NETIF_HOSTNAME. There is no need to calldhcp_renew()to propagate a hostname change that never happens.wifi_apply_hostname_()is only called from:setup_()— during initial setup before WiFi connectswifi_sta_connect_()— during the connection sequence, beforewifi_station_connect()In neither case is a
dhcp_renew()appropriate.How it causes damage
lwIP's
dhcp_renew()unconditionally sets the DHCP state toRENEWING(dhcp.c:1159) before attempting to send, and never rolls back on failure. When called on a disconnected interface:dhcp_network_changed()sees RENEWING and callsdhcp_reboot()instead ofdhcp_discover()dhcp_reboot()broadcasts a DHCP REQUEST for IP 0.0.0.0 (no valid lease)Fix
Remove the
dhcp_renew()loop entirely. Keep theintf->hostnameassignment which is the only part that matters.Types of changes
Related issue or feature (if applicable):
Pull request in esphome-docs with documentation (if applicable):
Test Environment
Example entry for
config.yaml:# No configuration changes — this is a bugfix in the WiFi connection internalsChecklist:
tests/folder).If user exposed functionality or configuration variables are added/changed: