c015a375b1
LinkDevice trait gains set_mtu(mtu) with default no-op. EthernetLink stores mtu in field, clamped to 576-9000 (RFC 791 min/max). netcfg/ifaces/eth0/mtu becomes rw: cat /scheme/netcfg/ifaces/eth0/mtu → '1500' (default) echo 1400 > .../mtu → VPN MTU echo 9000 > .../mtu → jumbo frames Clamped to [576, 9000]: - 576: minimum for IPv4 (RFC 791) - 9000: standard jumbo frame MTU Useful for VPNs (MTU 1400 to avoid fragmentation through tunnel overhead), jumbo frames (MTU 9000 on datacenter networks), and constrained paths.
886 lines
32 KiB
Rust
886 lines
32 KiB
Rust
#[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<Rule> {
|
|
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<RefCell<RouteTable>>,
|
|
devices: Rc<RefCell<DeviceList>>,
|
|
socket_set: Rc<RefCell<SocketSet>>,
|
|
ip_forward: Rc<Cell<bool>>,
|
|
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<String>, 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<Ipv4Address>, 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<Ipv6Address>, 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<Rule>, 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<IpCidr>, 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<bool>, None)
|
|
|| {
|
|
let val: u8 = if ip_forward.get() { 1 } else { 0 };
|
|
format!("{}\n", val)
|
|
}
|
|
|cur_value, line| {
|
|
let val = line.trim().parse::<u8>()
|
|
.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<EthernetAddress>, 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<IpCidr>, 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<usize>, 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::<usize>()
|
|
.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<Ipv6Address>,
|
|
}
|
|
|
|
type DNSConfigRef = Rc<RefCell<DNSConfig>>;
|
|
|
|
struct NetCfgFile {
|
|
path: String,
|
|
is_dir: bool,
|
|
is_writable: bool,
|
|
is_readable: bool,
|
|
node_writer: Option<Box<dyn NodeWriter>>,
|
|
read_buf: Vec<u8>,
|
|
write_buf: Vec<u8>,
|
|
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<RefCell<RouteTable>>,
|
|
devices: Rc<RefCell<DeviceList>>,
|
|
socket_set: Rc<RefCell<SocketSet>>,
|
|
ip_forward: Rc<Cell<bool>>,
|
|
observer: ObserverRef,
|
|
) -> Result<NetCfgScheme> {
|
|
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<Option<()>> {
|
|
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<Handle>,
|
|
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<usize> {
|
|
Ok(self.handles.insert(Handle::SchemeRoot))
|
|
}
|
|
|
|
fn openat(
|
|
&mut self,
|
|
dirfd: usize,
|
|
path: &str,
|
|
_flags: usize,
|
|
_fcntl_flags: u32,
|
|
ctx: &CallerCtx,
|
|
) -> SyscallResult<OpenResult> {
|
|
{
|
|
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<usize> {
|
|
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<usize> {
|
|
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<SyscallEventFlags> {
|
|
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))
|
|
}
|
|
}
|
|
}
|