net: dhcpv4: some lease-abandoning paths leave the bound address on the interface
Describe the bug
The DHCPv4 client removes the bound address in some paths that abandon a lease and not in others. When it skips the removal and then clears requested_ip, nothing refers to the address any more. It stays on the interface as NET_ADDR_DHCP, NET_ADDR_PREFERRED, with no lifetime (IPv4 addresses have no expiry timer). The next lease is added beside it.
Links are to main @ c605ea46.
| path | removes address? | code |
|---|---|---|
net_dhcpv4_stop() in BOUND / RENEWING |
yes | dhcpv4.c#L2038-L2045 |
net_dhcpv4_stop() in REBINDING |
no | dhcpv4.c#L2046-L2052 |
| NAK in RENEWING / REBINDING | yes | dhcpv4.c#L1568-L1577 |
| lease expiry in REBINDING | yes | dhcpv4.c#L882-L893 |
NET_EVENT_IF_DOWN in BOUND |
yes | dhcpv4.c#L1745-L1763 |
NET_EVENT_IF_DOWN in RENEWING / REBINDING |
no, only BOUND is handled | same |
dhcpv4_enter_selecting() |
no, clears requested_ip |
dhcpv4.c#L710-L729 (L724) |
How the address is orphaned:
net_dhcpv4_stop()in REBINDING leaves the address and keepsrequested_ip.net_dhcpv4_start()/net_dhcpv4_restart()from DISABLED enters INIT-REBOOT with thatrequested_ip(L1889-L1895; restart = stop + start, L2084-L2088).- On a different subnet the REQUEST is NAKed (L1545-L1549) or unanswered (L847-L852). Both call
dhcpv4_enter_selecting(), which clearsrequested_ip. - The old address now has no owner. With
CONFIG_NET_DHCPV4_INIT_REBOOT=nthe INIT arm does the same at once (L834-L835).
NET_EVENT_IF_DOWN in RENEWING/REBINDING is not permanent on its own: a later NAK or expiry still removes it. If the link returns on a different network, the old address stays preferred until one of those happens, up to the rest of the lease.
To Reproduce
Derived from the code above; not run in this exact form.
CONFIG_NET_DHCPV4=y,CONFIG_NET_DHCPV4_INIT_REBOOT=y,CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=2, net shell.- DHCP server on 198.51.100.0/24, short lease (e.g. 120 s). Wait for BOUND.
- Drop the client's DHCP traffic at the server so T1 and T2 pass unanswered. The client reaches REBINDING.
- From the application, call
net_dhcpv4_restart(iface). - Move the server to 192.0.2.0/24 and stop dropping. The INIT-REBOOT REQUEST for 198.51.100.x is NAKed or ignored.
- After BOUND:
net ifacelists twoDHCP preferredaddresses.DHCPv4 requestednames only the new one.
Expected behavior
Every path that abandons a lease removes the address it added. After a subnet change the interface holds one DHCP address.
Impact
- New connections to off-link hosts can pick the stale address as source and get no reply. Source selection accepts any used PREFERRED address (net_if.c#L4459-L4467).
- Lasts until reboot.
- Read from source, not run: with the default
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1the stale address holds the only slot.net_if_ipv4_addr_add()finds no free slot, and the ACK handler returns with only a debug log (dhcpv4.c#L1517-L1523). The client would never bind again.
Logs and console output
Wi-Fi station, LAN moved from 198.51.100.0/24 to 192.0.2.0/24 during one boot (uptime 12,543 s). Addresses replaced with documentation ranges.
IPv4 unicast addresses (max 2):
192.0.2.14/255.255.255.0 DHCP preferred
198.51.100.250/255.255.255.0 DHCP preferred
IPv4 gateway : 192.0.2.1
DHCPv4 state : bound, requested 192.0.2.14- Every new TCP connect timed out (
ETIMEDOUT). - A TCP connection opened before the change, to the same host, kept working.
- ARP held both 192.0.2.1 and 198.51.100.1 with the same MAC.
Not captured: the source address of the failing SYNs, and which path left the address. The application called net_dhcpv4_stop() / net_dhcpv4_restart() on lease loss and AP change. Long-lived TCP connections were open, so #119442 (deferred removal stays PREFERRED) is an equally possible route to the same state.
Environment
- OS: Windows 11 (host)
- Toolchain: Zephyr SDK 1.0.1
- Commit SHA or Version used: observed on v4.4.0 (
684c9e8f),esp32c6_devkitc/esp32c6/hpcore, Wi-Fi STA. Code paths re-checked on mainc605ea46e15fb9bdd518670258a8a52d8850d8f4.
Additional context
Suggested fix (dhcpv4.c):
- Track whether the client owns an address on the interface. Set it on the successful
net_if_ipv4_addr_add()in the ACK handler. - One helper removes
requested_ipif owned and clears the flag. - Call it in
net_dhcpv4_stop()for every state, inNET_EVENT_IF_DOWNfor RENEWING/REBINDING as for BOUND, and at the top ofdhcpv4_enter_selecting(). The last covers the NAK, INIT-REBOOT fallback and INIT callers, and any future caller.
The flag avoids removing an offered address that was never added (REQUESTING).
Related: #114794, #114793 (INIT-REBOOT on a network change). Their fixes reach dhcpv4_enter_selecting() sooner; neither removes the address.
Source: zephyrproject-rtos/zephyr