#[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> { 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); } if adapters.is_empty() { // No NIC present is a normal state on a diskless/headless/bare machine, // or simply before an unsupported NIC's driver attaches. Report it as // info and let the caller exit cleanly instead of emitting a spurious // ERROR + non-zero exit on every boot of a NIC-less system. info!("no network adapter found; network stack idle"); return Ok(adapters); } info!("Found {} network adapter(s): {:?}", adapters.len(), adapters); Ok(adapters) } fn run(daemon: daemon::Daemon) -> Result<()> { let adapters = get_all_network_adapters()?; if adapters.is_empty() { // Nothing to service; exit cleanly (see get_all_network_adapters). 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::::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); }