From c3f8c8985686c9453b66c28b0bfa575ff366790c Mon Sep 17 00:00:00 2001 From: vasilito Date: Sat, 25 Jul 2026 16:04:04 +0900 Subject: [PATCH] redbear-netctl: wait for interface to appear before waiting for DHCP address wait_for_address assumed eth0 already existed and polled for an address with a 1s window -> on a fresh boot (smolnetd/driver-manager bring the interface up async) it spun on a non-existent interface and logged "timed out waiting for DHCP address on eth0". Now: wait (bounded 20s) for /scheme/netcfg/ifaces/ to appear first, then wait for a DHCP lease with a realistic 8s window (was 1s, too short for a full DISCOVER/OFFER/REQUEST/ACK). Both bounded + oneshot_async so boot never blocks. Pairs with the smolnetd/dhcpd async-NIC-attach fix. --- .../system/redbear-netctl/source/src/main.rs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/local/recipes/system/redbear-netctl/source/src/main.rs b/local/recipes/system/redbear-netctl/source/src/main.rs index 437daaa77a..6d1bee346a 100644 --- a/local/recipes/system/redbear-netctl/source/src/main.rs +++ b/local/recipes/system/redbear-netctl/source/src/main.rs @@ -542,7 +542,10 @@ fn dhcp_wait_timeout() -> Duration { .ok() .and_then(|value| value.parse::().ok()) .map(Duration::from_millis) - .unwrap_or_else(|| Duration::from_millis(1000)) + // 8s: a real DHCP DISCOVER/OFFER/REQUEST/ACK handshake over a freshly + // brought-up link needs more than the old 1s (which expired before the + // lease ever arrived). + .unwrap_or_else(|| Duration::from_millis(8000)) } fn dhcp_poll_interval() -> Duration { @@ -553,10 +556,42 @@ fn dhcp_poll_interval() -> Duration { .unwrap_or_else(|| Duration::from_millis(50)) } +fn iface_appear_timeout() -> Duration { + env::var("REDBEAR_NETCTL_IFACE_WAIT_MS") + .ok() + .and_then(|value| value.parse::().ok()) + .map(Duration::from_millis) + .unwrap_or_else(|| Duration::from_millis(20_000)) +} + +fn interface_exists(interface: &str) -> bool { + netcfg_root() + .join("ifaces") + .join(interface) + .join("mac") + .exists() +} + fn wait_for_address(interface: &str) -> Result<(), String> { - let deadline = Instant::now() + dhcp_wait_timeout(); let poll = dhcp_poll_interval(); + // The NIC driver + smolnetd bring /scheme/netcfg/ifaces/ up + // ASYNCHRONOUSLY (driver-manager attaches drivers after this service + // starts), so on a fresh boot the interface may not exist yet. Wait for it + // to appear FIRST, then for a DHCP-assigned address. Both bounded so a + // NIC-less / no-DHCP machine still returns (this runs oneshot_async, so the + // wait never blocks boot/login). + let iface_deadline = Instant::now() + iface_appear_timeout(); + while !interface_exists(interface) { + if Instant::now() >= iface_deadline { + return Err(format!( + "interface {interface} never appeared in /scheme/netcfg (no NIC, or its driver failed to attach)" + )); + } + thread::sleep(poll); + } + + let deadline = Instant::now() + dhcp_wait_timeout(); loop { match current_addr(interface).as_deref() { Some(addr) if addr != "Not configured" && !addr.is_empty() => return Ok(()),