Files
RedBear-OS/netstack/src/main.rs
T
Red Bear OS 2e5e516587 net: wait for async NIC attach instead of one-shot-exit (fixes no-eth0 boot)
driver-manager attaches NIC drivers asynchronously, so on a fresh boot the
network.* scheme does not exist yet when smolnetd/dhcpd run (both gated only on
the driver-manager service having *started*). smolnetd scanned /scheme once,
found nothing, and exited -> /scheme/netcfg never came up -> dhcpd/netctl failed
with "Can't open /scheme/netcfg/ifaces/eth0/mac" and DHCP timed out.

- smolnetd: poll (bounded 20s) for a network.* adapter to appear before giving
  up; oneshot_async so this never blocks boot. Give-up log now distinguishes
  genuinely-NIC-less from a NIC-present-but-driver-failed (stale driver) case.
- dhcpd: wait (bounded 20s) for /scheme/netcfg/ifaces/eth0/mac to appear before
  attempting DHCP, instead of one-shot "Can't open"; exit clean if no NIC.
2026-07-25 16:02:50 +09:00

271 lines
9.1 KiB
Rust

#[macro_use]
extern crate log;
use std::process;
use anyhow::{anyhow, Context, Result};
use event::{EventFlags, EventQueue};
use libredox::flag::{O_NONBLOCK, O_RDWR};
use libredox::Fd;
use redox_scheme::Socket;
use scheme::Smolnetd;
use smoltcp::wire::EthernetAddress;
mod buffer_pool;
mod error;
mod filter;
mod icmp_error;
mod link;
mod logger;
mod observer;
mod port_set;
mod router;
mod scheme;
mod slaac;
fn get_all_network_adapters() -> Result<Vec<String>> {
use std::fs;
let mut adapters = vec![];
for entry_res in fs::read_dir("/scheme")? {
let Ok(entry) = entry_res else { continue };
let Ok(scheme) = entry.file_name().into_string() else { continue };
if !scheme.starts_with("network") { continue; }
adapters.push(scheme);
}
Ok(adapters)
}
/// Wait for at least one NIC driver to register its `network.*` scheme.
///
/// driver-manager attaches drivers ASYNCHRONOUSLY, so on a real boot the NIC's
/// `network.*` scheme does not exist yet when smolnetd starts (10_smolnetd only
/// requires_weak that driver-manager has *started*). The previous code scanned
/// `/scheme` exactly once and, finding nothing, exited — losing the race with
/// virtio-netd and leaving `/scheme/netcfg` absent, so dhcpd/netctl failed with
/// "Can't open /scheme/netcfg/ifaces/eth0/mac". Poll for a bounded window
/// instead. smolnetd is oneshot_async, so this wait never blocks init/boot; a
/// genuinely NIC-less machine simply gets an empty list after the window and
/// exits cleanly.
fn wait_for_network_adapters() -> Vec<String> {
use std::time::{Duration, Instant};
let deadline = Instant::now() + Duration::from_secs(20);
let mut logged_wait = false;
loop {
match get_all_network_adapters() {
Ok(adapters) if !adapters.is_empty() => {
info!("Found {} network adapter(s): {:?}", adapters.len(), adapters);
return adapters;
}
_ => {}
}
if Instant::now() >= deadline {
return Vec::new();
}
if !logged_wait {
info!("no network adapter yet; waiting for a NIC driver to attach...");
logged_wait = true;
}
std::thread::sleep(Duration::from_millis(250));
}
}
fn run(daemon: daemon::Daemon) -> Result<()> {
let adapters = wait_for_network_adapters();
if adapters.is_empty() {
// No `network.*` scheme registered within the wait window. Two cases:
// (a) genuinely NIC-less machine -> expected, harmless;
// (b) a NIC IS present but its driver never registered `network.*`
// -> a driver bug or a STALE/broken NIC driver binary that no
// longer speaks the current scheme protocol.
// If this box has a NIC, this is case (b): rebuild the net driver
// (virtio-netd / e1000d / rtl8168d). Exit cleanly either way (smolnetd
// is oneshot_async, so waiting never blocked boot/login).
warn!(
"no network.* scheme after waiting; network stack idle. If a NIC is \
present, its driver failed to register it (driver bug or stale driver binary)."
);
return Ok(());
}
trace!("found {} network adapter(s)", adapters.len());
let mut network_fds = Vec::new();
for name in &adapters {
let fd = Fd::open(&format!("/scheme/{name}"), O_RDWR | O_NONBLOCK, 0)
.map_err(|e| anyhow!("failed to open {name}: {e}"))?;
let hw = std::fs::read(format!("/scheme/{name}/mac"))
.map(|mac| EthernetAddress::from_bytes(&mac))
.with_context(|| format!("failed to get mac for {name}"))?;
network_fds.push((fd, hw, name.clone()));
}
trace!("opening ip scheme socket");
let ip_fd = Socket::nonblock()
.map_err(|e| anyhow!("failed to open create ip scheme socket: {:?}", e))?;
trace!("opening udp scheme socket");
let udp_fd =
Socket::nonblock().map_err(|e| anyhow!("failed to open udp scheme socket: {:?}", e))?;
trace!("opening tcp scheme socket");
let tcp_fd =
Socket::nonblock().map_err(|e| anyhow!("failed to open tcp scheme socket: {:?}", e))?;
trace!("opening icmp scheme socket");
let icmp_fd =
Socket::nonblock().map_err(|e| anyhow!("failed to open icmp scheme socket: {:?}", e))?;
trace!("opening netcfg scheme socket");
let netcfg_fd =
Socket::nonblock().map_err(|e| anyhow!("failed to open netcfg scheme socket {:?}", e))?;
trace!("opening netfilter scheme socket");
let netfilter_fd =
Socket::nonblock().map_err(|e| anyhow!("failed to open netfilter scheme socket: {:?}", e))?;
trace!("opening tun scheme socket");
let tun_fd =
Socket::nonblock().map_err(|e| anyhow!("failed to open tun scheme socket: {:?}", e))?;
let time_path = format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC);
let time_fd = Fd::open(&time_path, O_RDWR, 0).context("failed to open /scheme/time")?;
event::user_data! {
enum EventSource {
Network,
Time,
IpScheme,
UdpScheme,
TcpScheme,
IcmpScheme,
NetcfgScheme,
NetfilterScheme,
TunScheme,
}
}
let event_queue = EventQueue::<EventSource>::new()
.map_err(|e| anyhow!("failed to create event queue: {:?}", e))?;
daemon.ready();
// Subscribe to the first adapter for I/O events; the router handles
// packet dispatch across all adapters in the DeviceList.
if let Some((first_fd, _, _)) = network_fds.first() {
event_queue
.subscribe(first_fd.raw(), EventSource::Network, EventFlags::READ)
.map_err(|e| anyhow!("failed to listen to network events: {:?}", e))?;
}
event_queue
.subscribe(time_fd.raw(), EventSource::Time, EventFlags::READ)
.map_err(|e| anyhow!("failed to listen to timer events: {:?}", e))?;
event_queue
.subscribe(ip_fd.inner().raw(), EventSource::IpScheme, EventFlags::READ)
.map_err(|e| anyhow!("failed to listen to ip scheme events: {:?}", e))?;
event_queue
.subscribe(
udp_fd.inner().raw(),
EventSource::UdpScheme,
EventFlags::READ,
)
.map_err(|e| anyhow!("failed to listen to udp scheme events: {:?}", e))?;
event_queue
.subscribe(
tcp_fd.inner().raw(),
EventSource::TcpScheme,
EventFlags::READ,
)
.map_err(|e| anyhow!("failed to listen to tcp scheme events: {:?}", e))?;
event_queue
.subscribe(
icmp_fd.inner().raw(),
EventSource::IcmpScheme,
EventFlags::READ,
)
.map_err(|e| anyhow!("failed to listen to icmp scheme events: {:?}", e))?;
event_queue
.subscribe(
netcfg_fd.inner().raw(),
EventSource::NetcfgScheme,
EventFlags::READ,
)
.map_err(|e| anyhow!("failed to listen to netcfg scheme events: {:?}", e))?;
event_queue
.subscribe(
netfilter_fd.inner().raw(),
EventSource::NetfilterScheme,
EventFlags::READ,
)
.map_err(|e| anyhow!("failed to listen to netfilter scheme events: {:?}", e))?;
event_queue
.subscribe(
tun_fd.inner().raw(),
EventSource::TunScheme,
EventFlags::READ,
)
.map_err(|e| anyhow!("failed to listen to tun scheme events: {:?}", e))?;
let mut smolnetd = Smolnetd::new(
network_fds,
ip_fd,
udp_fd,
tcp_fd,
icmp_fd,
time_fd,
netcfg_fd,
netfilter_fd,
tun_fd,
)
.context("smolnetd: failed to initialize smolnetd")?;
libredox::call::setrens(0, 0).context("smolnetd: failed to enter null namespace")?;
let all = {
use EventSource::*;
[Network, Time, IpScheme, UdpScheme, TcpScheme, IcmpScheme, NetcfgScheme, NetfilterScheme, TunScheme].map(Ok)
};
for event_res in all
.into_iter()
.chain(event_queue.map(|r| r.map(|e| e.user_data)))
{
match event_res.map_err(|e| anyhow!("event result is error: {:?}", e))? {
EventSource::Network => smolnetd.on_network_scheme_event(),
EventSource::Time => smolnetd.on_time_event(),
EventSource::IpScheme => smolnetd.on_ip_scheme_event(),
EventSource::UdpScheme => smolnetd.on_udp_scheme_event(),
EventSource::TcpScheme => smolnetd.on_tcp_scheme_event(),
EventSource::IcmpScheme => smolnetd.on_icmp_scheme_event(),
EventSource::NetcfgScheme => smolnetd.on_netcfg_scheme_event(),
EventSource::NetfilterScheme => smolnetd.on_netfilter_scheme_event(),
EventSource::TunScheme => smolnetd.on_tun_scheme_event(),
}
.map_err(|e| error!("Received packet error: {:?}", e));
}
Ok(())
}
fn main() {
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
logger::init_logger("smolnetd");
if let Err(err) = run(daemon) {
error!("smoltcpd: {}", err);
process::exit(1);
}
process::exit(0);
}