#[macro_use] mod nodes; mod notifier; use redox_scheme::{ scheme::{register_scheme_inner, SchemeState, SchemeSync}, CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket, }; use scheme_utils::HandleMap; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; use std::cell::{Cell, RefCell}; use std::collections::BTreeMap; use std::mem; use std::rc::Rc; use std::str; use std::str::FromStr; use syscall; use syscall::data::Stat; use syscall::flag::{MODE_DIR, MODE_FILE}; use syscall::schemev2::NewFdFlags; use syscall::{Error as SyscallError, EventFlags as SyscallEventFlags, Result as SyscallResult}; use crate::error::{Error, Result}; use crate::link::DeviceList; use crate::observer::ObserverRef; use crate::router::route_table::{RouteTable, Rule}; use self::nodes::*; use self::notifier::*; use super::{post_fevent, Interface, SocketSet}; const WRITE_BUFFER_MAX_SIZE: usize = 0xffff; fn gateway_cidr() -> IpCidr { // TODO: const fn IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0) } fn cidr_network(cidr: IpCidr) -> IpCidr { match cidr { IpCidr::Ipv4(c) => IpCidr::Ipv4(c.network()), IpCidr::Ipv6(c) => { let prefix = c.prefix_len(); let bytes = c.address().octets(); let mut masked = [0u8; 16]; let full_bytes = (prefix / 8) as usize; let remainder = prefix % 8; for i in 0..full_bytes { masked[i] = bytes[i]; } if remainder > 0 && full_bytes < 16 { masked[full_bytes] = bytes[full_bytes] & (0xff << (8 - remainder)); } let segments: [u16; 8] = core::array::from_fn(|i| { u16::from_be_bytes([masked[i * 2], masked[i * 2 + 1]]) }); IpCidr::Ipv6(smoltcp::wire::Ipv6Cidr::new( Ipv6Address::new( segments[0], segments[1], segments[2], segments[3], segments[4], segments[5], segments[6], segments[7], ), prefix, )) } } } fn parse_route(value: &str, route_table: &RouteTable) -> SyscallResult { let mut parts = value.split_whitespace(); let cidr_str = parts.next().ok_or(SyscallError::new(syscall::EINVAL))?; let cidr = match cidr_str { "default" => gateway_cidr(), cidr_str => cidr_str .parse() .map_err(|_| SyscallError::new(syscall::EINVAL))?, }; let via: IpAddress = match parts.next().ok_or(SyscallError::new(syscall::EINVAL))? { "via" => parts .next() .ok_or(SyscallError::new(syscall::EINVAL))? .parse() .map_err(|_| SyscallError::new(syscall::EINVAL))?, _ => return Err(SyscallError::new(syscall::EINVAL)), }; if !via.is_unicast() { return Err(SyscallError::new(syscall::EINVAL)); } let rule = route_table .lookup_rule(&via) .ok_or(SyscallError::new(syscall::EINVAL))?; Ok(Rule::new(cidr, Some(via), rule.dev.clone(), rule.src)) } fn mk_root_node( iface: Interface, notifier: NotifierRef, dns_config: DNSConfigRef, route_table: Rc>, devices: Rc>, socket_set: Rc>, ip_forward: Rc>, observer: ObserverRef, ) -> CfgNodeRef { cfg_node! { "capture" => { "enable" => { wo [observer] (Option<()>, None) |_cur_value, _line| { Ok(()) } |_cur_value| { observer.enable(); Ok(()) } }, "disable" => { wo [observer] (Option<()>, None) |_cur_value, _line| { Ok(()) } |_cur_value| { observer.disable(); Ok(()) } }, "read" => { ro [observer] || { if observer.is_enabled() { observer.drain_hex() } else { "capture disabled (echo to /scheme/netcfg/capture/enable to start)\n".to_string() } } }, "filter" => { rw [observer] (Option, None) || { format!("{}\n", observer.filter_str()) } |cur_value, line| { let s = line.trim().to_string(); observer.set_filter(&s); *cur_value = Some(s); Ok(()) } |_cur_value| { Ok(()) } }, "count" => { ro [observer] || { format!("captured={} buffered={} enabled={}\n", observer.total(), observer.len(), observer.is_enabled()) } }, }, "sockets" => { "list" => { ro [socket_set] || { let set = socket_set.borrow(); format!("sockets: {}\n", set.iter().count()) } } }, "resolv" => { "nameserver" => { rw [dns_config, notifier] (Option, None) || { format!("{}\n", dns_config.borrow().name_server) } |cur_value, line| { if cur_value.is_none() { let ip = Ipv4Address::from_str(line.trim()) .map_err(|_| SyscallError::new(syscall::EINVAL))?; if ip.is_broadcast() || ip.is_multicast() || ip.is_unspecified() { return Err(SyscallError::new(syscall::EINVAL)); } *cur_value = Some(ip); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } |cur_value| { if let Some(ip) = *cur_value { dns_config.borrow_mut().name_server = ip; notifier.borrow_mut().schedule_notify("resolv/nameserver"); } Ok(()) } }, "nameserver6" => { rw [dns_config, notifier] (Option, None) || { match dns_config.borrow().name_server6 { Some(ip) => format!("{}\n", ip), None => "Not configured\n".into(), } } |cur_value, line| { if cur_value.is_none() { let ip = Ipv6Address::from_str(line.trim()) .map_err(|_| SyscallError::new(syscall::EINVAL))?; if ip.is_multicast() || ip.is_unspecified() { return Err(SyscallError::new(syscall::EINVAL)); } *cur_value = Some(ip); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } |cur_value| { if let Some(ip) = *cur_value { dns_config.borrow_mut().name_server6 = Some(ip); notifier.borrow_mut().schedule_notify("resolv/nameserver6"); } Ok(()) } } }, "route" => { "list" => { ro [route_table] || { format!("{}", route_table.borrow()) } }, "add" => { wo [iface, notifier, route_table] (Option, None) |cur_value, line| { if cur_value.is_none() { let route = parse_route(line, &route_table.borrow())?; *cur_value = Some(route); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } |cur_value| { if let Some(route) = cur_value.take() { route_table.borrow_mut().insert_rule(route); notifier.borrow_mut().schedule_notify("route/list"); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } }, "rm" => { wo [iface, notifier, route_table] (Option, None) |cur_value, line| { if cur_value.is_none() { match line.parse() { Ok(cidr) => { *cur_value = Some(cidr); Ok(()) } Err(_) => Err(SyscallError::new(syscall::EINVAL)) } } else { Err(SyscallError::new(syscall::EINVAL)) } } |cur_value| { if let Some(cidr) = *cur_value { route_table.borrow_mut().remove_rule(cidr); notifier.borrow_mut().schedule_notify("route/list"); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } }, }, "sysctl" => { "net" => { "ipv4" => { "ip_forward" => { rw [ip_forward] (Option, None) || { let val: u8 = if ip_forward.get() { 1 } else { 0 }; format!("{}\n", val) } |cur_value, line| { let val = line.trim().parse::() .map_err(|_| SyscallError::new(syscall::EINVAL))?; match val { 0 => { ip_forward.set(false); } 1 => { ip_forward.set(true); } _ => return Err(SyscallError::new(syscall::EINVAL)), } *cur_value = Some(val == 1); Ok(()) } |_cur_value| { Ok(()) } } } } }, "ifaces" => { "eth0" => { "mac" => { rw [iface, notifier, devices] (Option, None) || { match devices.borrow().get("eth0") { Some(dev) => { match dev.mac_address() { Some(addr) => format!("{addr}\n"), None => "Not configured\n".into(), } } None => "Device not found\n".into(), } } |cur_value, line| { if cur_value.is_none() { let mac = EthernetAddress::from_str(line). map_err(|_| SyscallError::new(syscall::EINVAL))?; if !mac.is_unicast() { return Err(SyscallError::new(syscall::EINVAL)); } *cur_value = Some(mac); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } |cur_value| { if let Some(mac) = *cur_value { if let Some(dev) = devices.borrow_mut().get_mut("eth0") { dev.set_mac_address(mac); notifier.borrow_mut().schedule_notify("ifaces/eth0/mac"); } } Ok(()) } }, "addr" => { "list" => { ro [devices] || { let res = match devices.borrow().get("eth0") { Some(dev) => { match dev.ip_address() { Some(addr) => format!("{addr}\n"), None => "Not configured\n".into(), } } None => "Device not found\n".into(), }; res } }, "set" => { wo [iface, notifier, devices, route_table] (Option, None) |cur_value, line| { if cur_value.is_none() { let cidr = IpCidr::from_str(line) .map_err(|_| SyscallError::new(syscall::EINVAL))?; if !cidr.address().is_unicast() { return Err(SyscallError::new(syscall::EINVAL)); } *cur_value = Some(cidr); Ok(()) } else { Err(SyscallError::new(syscall::EINVAL)) } } |cur_value| { // TODO: Multiple IPs if let Some(cidr) = cur_value.take() { if let Some(dev) = devices.borrow_mut().get_mut("eth0") { let mut route_table = route_table.borrow_mut(); if let Some(old_addr) = dev.ip_address() { let old_network = cidr_network(old_addr); route_table.remove_rule(old_network); route_table.change_src(old_addr.address(), cidr.address()); iface.borrow_mut().update_ip_addrs(|addrs| addrs.retain(|addr| *addr != old_addr)) } dev.set_ip_address(cidr); // FIXME: Here, the insert 0 is a workaround to let UDP sockets // work with this interface only. // Smoltcp takes the first ip address when looking for a source // ip address when sending UDP packets. // This behavior will have to be fixed as it's our route table // job to find give this source. iface.borrow_mut().update_ip_addrs(|addrs| addrs.insert(0, cidr).unwrap()); let network_cidr = cidr_network(cidr); route_table.insert_rule(Rule::new(network_cidr, None, dev.name().clone(), cidr.address())) } notifier.borrow_mut().schedule_notify("ifaces/eth0/addr/list"); notifier.borrow_mut().schedule_notify("route/list"); } Ok(()) } }, }, "arp" => { "list" => { ro [devices] || { match devices.borrow().get("eth0") { Some(dev) => dev.arp_table(), None => "Device not found\n".into(), } } }, "stats" => { ro [devices] || { match devices.borrow().get("eth0") { Some(dev) => dev.arp_stats(), None => "Device not found\n".into(), } } }, "flush" => { wo [devices] (Option<()>, None) |_cur_value, _line| { Ok(()) } |cur_value| { *cur_value = None; if let Some(dev) = devices.borrow_mut().get_mut("eth0") { dev.flush_arp(); } Ok(()) } }, }, "stats" => { ro [devices] || { match devices.borrow().get("eth0") { Some(dev) => { let s = dev.statistics(); format!("rx_bytes={} rx_packets={} tx_bytes={} tx_packets={} mtu={} link={}\n", s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets, dev.mtu(), dev.link_state()) } None => "Device not found\n".into(), } } }, "link" => { ro [devices] || { match devices.borrow().get("eth0") { Some(dev) => format!("{}\n", dev.link_state()), None => "Device not found\n".into(), } } }, "mtu" => { rw [devices] (Option, None) || { match devices.borrow().get("eth0") { Some(dev) => format!("{}\n", dev.mtu()), None => "Device not found\n".into(), } } |cur_value, line| { let mtu = line.trim().parse::() .map_err(|_| SyscallError::new(syscall::EINVAL))?; if let Some(dev) = devices.borrow_mut().get_mut("eth0") { dev.set_mtu(mtu); *cur_value = Some(mtu); Ok(()) } else { Err(SyscallError::new(syscall::ENODEV)) } } |_cur_value| { Ok(()) } }, "qdisc" => { ro [devices] || { match devices.borrow().get("eth0") { Some(dev) => dev.qdisc_info(), None => "Device not found\n".into(), } } } }, "lo" => { "addr" => { "list" => { ro [devices] || { match devices.borrow().get("loopback") { Some(dev) => match dev.ip_address() { Some(addr) => format!("{addr}\n"), None => "Not configured\n".into(), }, None => "Device not found\n".into(), } } } }, "stats" => { ro [devices] || { match devices.borrow().get("loopback") { Some(dev) => { let s = dev.statistics(); format!("rx_bytes={} rx_packets={} tx_bytes={} tx_packets={} mtu={} link={}\n", s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets, dev.mtu(), dev.link_state()) } None => "Device not found\n".into(), } } }, } } } } struct DNSConfig { name_server: Ipv4Address, name_server6: Option, } type DNSConfigRef = Rc>; struct NetCfgFile { path: String, is_dir: bool, is_writable: bool, is_readable: bool, node_writer: Option>, read_buf: Vec, write_buf: Vec, pos: usize, uid: u32, done: bool, } impl NetCfgFile { fn commit(&mut self) -> SyscallResult<()> { if let Some(ref mut node_writer) = self.node_writer { if !self.write_buf.is_empty() { let line = str::from_utf8(&self.write_buf) .or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?; node_writer.write_line(line)?; } node_writer.commit()?; self.write_buf.clear(); } Ok(()) } fn consume_lines(&mut self) -> SyscallResult<()> { if let Some(ref mut node_writer) = self.node_writer { let mut swap_with = None; { let mut lines = self.write_buf.split(|&c| c == b'\n'); if let Some(mut cur_line) = lines.next() { let mut consumed = false; for next_line in lines { let line = str::from_utf8(cur_line) .or_else(|_| Err(SyscallError::new(syscall::EINVAL)))?; trace!("writing line {}", line); node_writer.write_line(line)?; cur_line = next_line; consumed = true; } if consumed { swap_with = Some(From::from(cur_line)) } } } if let Some(ref mut new_vec) = swap_with { mem::swap(&mut self.write_buf, new_vec); } Ok(()) } else { Err(SyscallError::new(syscall::EBADF)) } } } pub struct NetCfgScheme { inner: NetCfgSchemeInner, state: SchemeState, } impl NetCfgScheme { pub fn new( iface: Interface, scheme_file: Socket, route_table: Rc>, devices: Rc>, socket_set: Rc>, ip_forward: Rc>, observer: ObserverRef, ) -> Result { let notifier = Notifier::new_ref(); let dns_config = Rc::new(RefCell::new(DNSConfig { name_server: Ipv4Address::new(8, 8, 8, 8), name_server6: None, })); let mut inner = NetCfgSchemeInner { scheme_file, handles: HandleMap::new(), root_node: mk_root_node( iface, Rc::clone(¬ifier), dns_config, route_table, devices, socket_set, ip_forward, observer, ), notifier, }; let cap_id = inner .scheme_root() .map_err(|e| Error::from_syscall_error(e, "failed to get scheme root id"))?; register_scheme_inner(&inner.scheme_file, "netcfg", cap_id).map_err(|e| { Error::from_syscall_error(e, "failed to register netcfg scheme to namespace") })?; Ok(Self { inner, state: SchemeState::new(), }) } pub fn on_scheme_event(&mut self) -> Result> { let result = loop { let request = match self.inner.scheme_file.next_request(SignalBehavior::Restart) { Ok(Some(req)) => req, Ok(None) => { break Some(()); } Err(error) if error.errno == syscall::EWOULDBLOCK || error.errno == syscall::EAGAIN => { break None; } Err(other) => { return Err(Error::from_syscall_error( other, "failed to receive new request", )) } }; match request.kind() { RequestKind::Call(c) => { let resp = c.handle_sync(&mut self.inner, &mut self.state); let _ = self .inner .scheme_file .write_response(resp, SignalBehavior::Restart) .map_err(|e| { Error::from_syscall_error(e.into(), "failed to write response") })?; } RequestKind::OnClose { id } => { self.inner.on_close(id); } _ => {} } }; self.inner.notify_scheduled_fds(); Ok(result) } } enum Handle { SchemeRoot, File(NetCfgFile), } struct NetCfgSchemeInner { scheme_file: Socket, handles: HandleMap, root_node: CfgNodeRef, notifier: NotifierRef, } impl NetCfgSchemeInner { fn notify_scheduled_fds(&mut self) { let fds_to_notify = self.notifier.borrow_mut().get_notified_fds(); for fd in fds_to_notify { let _ = post_fevent(&self.scheme_file, fd, syscall::EVENT_READ.bits()); } } } impl SchemeSync for NetCfgSchemeInner { fn scheme_root(&mut self) -> SyscallResult { Ok(self.handles.insert(Handle::SchemeRoot)) } fn openat( &mut self, dirfd: usize, path: &str, _flags: usize, _fcntl_flags: u32, ctx: &CallerCtx, ) -> SyscallResult { { let handle = self.handles.get(dirfd)?; if !matches!(handle, Handle::SchemeRoot) { return Err(SyscallError::new(syscall::EACCES)); } } let mut current_node = Rc::clone(&self.root_node); for part in path.split('/') { if part.is_empty() { continue; } let next_node = current_node .borrow_mut() .open(part) .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; current_node = next_node; } let current_node = current_node.borrow(); let read_buf = Vec::from(current_node.read()); let fd = self.handles.insert(Handle::File(NetCfgFile { path: path.to_owned(), is_dir: current_node.is_dir(), is_writable: current_node.is_writable(), is_readable: current_node.is_readable(), node_writer: if current_node.is_writable() { current_node.new_writer() } else { None }, uid: ctx.uid, pos: 0, read_buf, write_buf: vec![], done: false, })); trace!("open {} {}", fd, path); Ok(OpenResult::ThisScheme { number: fd, flags: NewFdFlags::empty(), }) } fn on_close(&mut self, fd: usize) { trace!("close {}", fd); if let Some(handle) = self.handles.remove(fd) { match handle { Handle::SchemeRoot => { // SchemeRoot closed, nothing specific to clean up } Handle::File(mut file) => { self.notifier.borrow_mut().unsubscribe(&file.path, fd); if !file.done { let _ = file.commit().map(|_| 0); } } } } } fn write( &mut self, fd: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx, ) -> SyscallResult { let handle = self.handles.get_mut(fd)?; let file = match handle { Handle::File(file) => file, Handle::SchemeRoot => return Err(SyscallError::new(syscall::EBADF)), }; if file.done { return Err(SyscallError::new(syscall::EBADF)); } if file.uid != 0 { return Err(SyscallError::new(syscall::EACCES)); } if (WRITE_BUFFER_MAX_SIZE - file.write_buf.len()) < buf.len() { return Err(SyscallError::new(syscall::EMSGSIZE)); } file.write_buf.extend_from_slice(buf); if let Err(e) = file.consume_lines() { trace!("Failed write {} {}", fd, e); file.done = true; return Err(e); } Ok(buf.len()) } fn read( &mut self, fd: usize, buf: &mut [u8], _offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx, ) -> SyscallResult { let handle = self.handles.get_mut(fd)?; let file = match handle { Handle::File(file) => file, Handle::SchemeRoot => return Err(SyscallError::new(syscall::EBADF)), }; let mut i = 0; while i < buf.len() && file.pos < file.read_buf.len() { buf[i] = file.read_buf[file.pos]; i += 1; file.pos += 1; } Ok(i) } fn fstat(&mut self, fd: usize, stat: &mut Stat, _ctx: &CallerCtx) -> SyscallResult<()> { let handle = self.handles.get_mut(fd)?; match handle { Handle::SchemeRoot => return Err(SyscallError::new(syscall::EBADF)), Handle::File(file) => { stat.st_mode = if file.is_dir { MODE_DIR } else { MODE_FILE }; if file.is_writable { stat.st_mode |= 0o222; } if file.is_readable { stat.st_mode |= 0o444; } stat.st_uid = 0; stat.st_gid = 0; stat.st_size = file.read_buf.len() as u64; } } Ok(()) } fn fevent( &mut self, fd: usize, events: SyscallEventFlags, _ctx: &CallerCtx, ) -> SyscallResult { let handle = self.handles.get_mut(fd)?; match handle { Handle::SchemeRoot => return Err(SyscallError::new(syscall::EBADF)), Handle::File(file) => { if events.contains(syscall::EVENT_READ) { self.notifier.borrow_mut().subscribe(&file.path, fd); } else { self.notifier.borrow_mut().unsubscribe(&file.path, fd); } } } Ok(SyscallEventFlags::empty()) } fn fsync(&mut self, fd: usize, _ctx: &CallerCtx) -> SyscallResult<()> { let handle = self.handles.get_mut(fd)?; let file = match handle { Handle::File(file) => file, Handle::SchemeRoot => return Err(SyscallError::new(syscall::EBADF)), }; if !file.done { let res = file.commit(); file.done = true; res } else { Err(SyscallError::new(syscall::EBADF)) } } }