#119441·zephyr

net: dhcpv4: some lease-abandoning paths leave the bound address on the interface

Author: ErsinErceCreated Sep 17, 2026Updated Sep 18, 2026
Labelsarea: Networking

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:

  1. net_dhcpv4_stop() in REBINDING leaves the address and keeps requested_ip.
  2. net_dhcpv4_start() / net_dhcpv4_restart() from DISABLED enters INIT-REBOOT with that requested_ip (L1889-L1895; restart = stop + start, L2084-L2088).
  3. On a different subnet the REQUEST is NAKed (L1545-L1549) or unanswered (L847-L852). Both call dhcpv4_enter_selecting(), which clears requested_ip.
  4. The old address now has no owner. With CONFIG_NET_DHCPV4_INIT_REBOOT=n the 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.

  1. CONFIG_NET_DHCPV4=y, CONFIG_NET_DHCPV4_INIT_REBOOT=y, CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=2, net shell.
  2. DHCP server on 198.51.100.0/24, short lease (e.g. 120 s). Wait for BOUND.
  3. Drop the client's DHCP traffic at the server so T1 and T2 pass unanswered. The client reaches REBINDING.
  4. From the application, call net_dhcpv4_restart(iface).
  5. 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.
  6. After BOUND: net iface lists two DHCP preferred addresses. DHCPv4 requested names 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=1 the 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 main c605ea46e15fb9bdd518670258a8a52d8850d8f4.

Additional context

Suggested fix (dhcpv4.c):

  1. Track whether the client owns an address on the interface. Set it on the successful net_if_ipv4_addr_add() in the ACK handler.
  2. One helper removes requested_ip if owned and clears the flag.
  3. Call it in net_dhcpv4_stop() for every state, in NET_EVENT_IF_DOWN for RENEWING/REBINDING as for BOUND, and at the top of dhcpv4_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