restore: networking stack files from reflog (Phases 1-6)
Recovered from reflog commits 1c80937e and d0ecc067 after force-push data loss.
Includes: filter/, icmp_error.rs, slaac.rs, bond.rs, bridge.rs, gre.rs, ipip.rs,
qdisc.rs, tun.rs, vlan.rs, vxlan.rs, netfilter.rs, tun.rs, conntrack.rs, nat.rs,
rule.rs, table.rs, redbear-ufw/, dhcpv6d/, netdiag/ — 39 files total.
This commit is contained in:
@@ -33,19 +33,25 @@ use syscall::Error as SyscallError;
|
||||
use self::icmp::IcmpScheme;
|
||||
use self::ip::IpScheme;
|
||||
use self::netcfg::NetCfgScheme;
|
||||
use self::netfilter::NetFilterScheme;
|
||||
use self::tcp::TcpScheme;
|
||||
use self::tun::TunScheme;
|
||||
use self::udp::UdpScheme;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::filter::{FilterTable, PacketContext, Verdict};
|
||||
|
||||
mod icmp;
|
||||
mod ip;
|
||||
mod netcfg;
|
||||
mod netfilter;
|
||||
mod socket;
|
||||
mod tcp;
|
||||
mod tun;
|
||||
mod udp;
|
||||
|
||||
type SocketSet = SmoltcpSocketSet<'static>;
|
||||
type Interface = Rc<RefCell<SmoltcpInterface>>;
|
||||
type FilterTableRef = Rc<RefCell<FilterTable>>;
|
||||
|
||||
const MAX_DURATION: Duration = Duration::from_micros(u64::MAX);
|
||||
const MIN_DURATION: Duration = Duration::from_micros(0);
|
||||
@@ -70,6 +76,8 @@ pub struct Smolnetd {
|
||||
tcp_scheme: TcpScheme,
|
||||
icmp_scheme: IcmpScheme,
|
||||
netcfg_scheme: NetCfgScheme,
|
||||
netfilter_scheme: NetFilterScheme,
|
||||
filter_table: FilterTableRef,
|
||||
}
|
||||
|
||||
impl Smolnetd {
|
||||
@@ -87,11 +95,13 @@ impl Smolnetd {
|
||||
icmp_file: Socket,
|
||||
time_file: Fd,
|
||||
netcfg_file: Socket,
|
||||
netfilter_file: Socket,
|
||||
) -> Result<Smolnetd> {
|
||||
let protocol_addrs = vec![
|
||||
//This is a placeholder IP for DHCP
|
||||
IpCidr::new(IpAddress::v4(0, 0, 0, 0), 8),
|
||||
IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8),
|
||||
IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128),
|
||||
];
|
||||
|
||||
let default_gw = Ipv4Address::from_str(getcfg("ip_router").unwrap().trim())
|
||||
@@ -114,6 +124,7 @@ impl Smolnetd {
|
||||
|
||||
let iface = Rc::new(RefCell::new(iface));
|
||||
let socket_set = Rc::new(RefCell::new(SocketSet::new(vec![])));
|
||||
let filter_table = Rc::new(RefCell::new(FilterTable::new()));
|
||||
|
||||
let loopback = LoopbackDevice::default();
|
||||
route_table.borrow_mut().insert_rule(Rule::new(
|
||||
@@ -122,6 +133,12 @@ impl Smolnetd {
|
||||
Rc::clone(loopback.name()),
|
||||
"127.0.0.1".parse().unwrap(),
|
||||
));
|
||||
route_table.borrow_mut().insert_rule(Rule::new(
|
||||
"::1/128".parse().unwrap(),
|
||||
None,
|
||||
Rc::clone(loopback.name()),
|
||||
"::1".parse().unwrap(),
|
||||
));
|
||||
|
||||
let mut eth0 = EthernetLink::new("eth0", unsafe {
|
||||
File::from_raw_fd(network_file.into_raw() as RawFd)
|
||||
@@ -172,6 +189,11 @@ impl Smolnetd {
|
||||
Rc::clone(&devices),
|
||||
Rc::clone(&socket_set),
|
||||
)?,
|
||||
netfilter_scheme: NetFilterScheme::new(
|
||||
netfilter_file,
|
||||
Rc::clone(&filter_table),
|
||||
)?,
|
||||
filter_table,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -216,6 +238,11 @@ impl Smolnetd {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_netfilter_scheme_event(&mut self) -> Result<()> {
|
||||
self.netfilter_scheme.on_scheme_event()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn schedule_time_event(&mut self, timeout: Duration) -> Result<()> {
|
||||
let mut time = TimeSpec::default();
|
||||
if self.time_file.read(&mut time)? < size_of::<TimeSpec>() {
|
||||
@@ -249,10 +276,22 @@ impl Smolnetd {
|
||||
|
||||
self.router_device.get_mut().poll(timestamp);
|
||||
|
||||
self.router_device.get_mut().forward_packets(&self.filter_table, timestamp);
|
||||
|
||||
// INPUT filter hook: drop packets before smoltcp processing.
|
||||
// Mirrors Linux's NF_INET_LOCAL_IN hook in iptable_filter.c.
|
||||
self.router_device.get_mut().filter_input(&self.filter_table, timestamp);
|
||||
|
||||
// TODO: Check what if the bool returned by poll can be useful
|
||||
iface.poll(timestamp, &mut self.router_device, &mut socket_set);
|
||||
|
||||
self.router_device.get_mut().dispatch(timestamp);
|
||||
self.router_device.get_mut().dispatch(timestamp, &self.filter_table);
|
||||
|
||||
self.filter_table
|
||||
.borrow_mut()
|
||||
.conntrack
|
||||
.as_mut()
|
||||
.map(|ct| ct.clean_expired(timestamp));
|
||||
|
||||
if !self.router_device.get_ref().can_recv() {
|
||||
match iface.poll_delay(timestamp, &socket_set) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use redox_scheme::{
|
||||
CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket,
|
||||
};
|
||||
use scheme_utils::HandleMap;
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
|
||||
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
@@ -35,6 +35,35 @@ fn gateway_cidr() -> IpCidr {
|
||||
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))?;
|
||||
@@ -109,6 +138,35 @@ fn mk_root_node(
|
||||
}
|
||||
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" => {
|
||||
@@ -240,8 +298,7 @@ fn mk_root_node(
|
||||
|
||||
let mut route_table = route_table.borrow_mut();
|
||||
if let Some(old_addr) = dev.ip_address() {
|
||||
let IpCidr::Ipv4(old_v4_cidr) = old_addr;
|
||||
let old_network = IpCidr::Ipv4(old_v4_cidr.network());
|
||||
let old_network = cidr_network(old_addr);
|
||||
|
||||
route_table.remove_rule(old_network);
|
||||
route_table.change_src(old_addr.address(), cidr.address());
|
||||
@@ -257,8 +314,7 @@ fn mk_root_node(
|
||||
// job to find give this source.
|
||||
iface.borrow_mut().update_ip_addrs(|addrs| addrs.insert(0, cidr).unwrap());
|
||||
|
||||
let IpCidr::Ipv4(v4_cidr) = cidr;
|
||||
let network_cidr = IpCidr::Ipv4(v4_cidr.network());
|
||||
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");
|
||||
@@ -267,7 +323,85 @@ fn mk_root_node(
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
},
|
||||
"arp" => {
|
||||
"list" => {
|
||||
ro [devices] || {
|
||||
match devices.borrow().get("eth0") {
|
||||
Some(dev) => dev.arp_table(),
|
||||
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={}\n",
|
||||
s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets)
|
||||
}
|
||||
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" => {
|
||||
ro [devices] || {
|
||||
match devices.borrow().get("eth0") {
|
||||
Some(dev) => format!("{}\n", dev.mtu()),
|
||||
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={}\n",
|
||||
s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets)
|
||||
}
|
||||
None => "Device not found\n".into(),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,6 +409,7 @@ fn mk_root_node(
|
||||
|
||||
struct DNSConfig {
|
||||
name_server: Ipv4Address,
|
||||
name_server6: Option<Ipv6Address>,
|
||||
}
|
||||
|
||||
type DNSConfigRef = Rc<RefCell<DNSConfig>>;
|
||||
@@ -352,6 +487,7 @@ impl 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,
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
//! Netfilter scheme: exposes the `filter` table over the `netfilter:` scheme.
|
||||
//!
|
||||
//! This is the user-facing control plane that mirrors how Linux exposes
|
||||
//! netfilter to userspace:
|
||||
//! - Linux: `iptables -A INPUT ...` writes a rule via the `nf_setsockopt`
|
||||
//! interface (`net/ipv4/netfilter/ip_tables.c:ipt_setsockopt`).
|
||||
//! - Linux: `nft add rule ...` writes via the netlink interface
|
||||
//! (`net/netfilter/nf_tables_api.c`).
|
||||
//!
|
||||
//! In Red Bear, both mechanisms are replaced by the `netfilter:` scheme,
|
||||
//! which exposes a filesystem-style API:
|
||||
//!
|
||||
//! ```text
|
||||
//! /netfilter/
|
||||
//! ├── rule/
|
||||
//! │ ├── list — read: text dump of every rule (mirrors `iptables -L`)
|
||||
//! │ ├── add — write: append one rule (mirrors `iptables -A`)
|
||||
//! │ └── del — write: remove one rule by id (mirrors `iptables -D`)
|
||||
//! ├── policy/
|
||||
//! │ ├── input — read/write: default policy for INPUT (ACCEPT/DROP)
|
||||
//! │ ├── output — read/write: default policy for OUTPUT (ACCEPT/DROP)
|
||||
//! │ ├── forward — read/write: default policy for FORWARD (ACCEPT/DROP)
|
||||
//! │ ├── prerouting — read/write: default policy for PREROUTING
|
||||
//! │ └── postrouting — read/write: default policy for POSTROUTING
|
||||
//! └── reset — write: clear all rules and restore default policies
|
||||
//! ```
|
||||
//!
|
||||
//! Each `add` returns the newly assigned numeric rule id on a successful
|
||||
//! `fsync` (the standard scheme write/commit convention used elsewhere
|
||||
//! in netstack, e.g. `netcfg/`).
|
||||
|
||||
use redox_scheme::{
|
||||
scheme::{register_scheme_inner, SchemeState, SchemeSync},
|
||||
CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket,
|
||||
};
|
||||
use scheme_utils::HandleMap;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
use syscall;
|
||||
use syscall::data::Stat;
|
||||
use syscall::flag::{MODE_DIR, MODE_FILE};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error as SyscallError, Result as SyscallResult};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::filter::{parse_nat_rule, parse_rule, FilterTable, Hook, Verdict};
|
||||
|
||||
const WRITE_BUFFER_MAX_SIZE: usize = 0xffff;
|
||||
|
||||
type FilterTableRef = Rc<RefCell<FilterTable>>;
|
||||
|
||||
pub struct NetFilterScheme {
|
||||
inner: NetFilterSchemeInner,
|
||||
state: SchemeState,
|
||||
}
|
||||
|
||||
impl NetFilterScheme {
|
||||
pub fn new(scheme_file: Socket, table: FilterTableRef) -> Result<NetFilterScheme> {
|
||||
let mut inner = NetFilterSchemeInner {
|
||||
scheme_file,
|
||||
handles: HandleMap::new(),
|
||||
table,
|
||||
};
|
||||
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, "netfilter", cap_id).map_err(|e| {
|
||||
Error::from_syscall_error(e, "failed to register netfilter 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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
enum Handle {
|
||||
SchemeRoot,
|
||||
File(NetFilterFile),
|
||||
}
|
||||
|
||||
struct NetFilterFile {
|
||||
path: String,
|
||||
is_dir: bool,
|
||||
is_writable: bool,
|
||||
is_readable: bool,
|
||||
read_buf: Vec<u8>,
|
||||
write_buf: Vec<u8>,
|
||||
pos: usize,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
struct NetFilterSchemeInner {
|
||||
scheme_file: Socket,
|
||||
handles: HandleMap<Handle>,
|
||||
table: FilterTableRef,
|
||||
}
|
||||
|
||||
impl NetFilterSchemeInner {
|
||||
fn on_close(&mut self, fd: usize) {
|
||||
if let Some(handle) = self.handles.remove(fd) {
|
||||
match handle {
|
||||
Handle::SchemeRoot => {}
|
||||
Handle::File(mut file) => {
|
||||
if !file.done {
|
||||
let _ = file.commit(&self.table);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_path(&mut self, path: &str) -> SyscallResult<usize> {
|
||||
let is_dir = path.is_empty() || path.ends_with('/');
|
||||
let parts: Vec<&str> = if is_dir {
|
||||
path.trim_end_matches('/').split('/').collect()
|
||||
} else {
|
||||
path.split('/').collect()
|
||||
};
|
||||
|
||||
let (read_buf, is_readable, is_writable) = match parts.as_slice() {
|
||||
[] | [""] => (
|
||||
self.table.borrow().format().into_bytes(),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
["rule", "list"] => (self.table.borrow().format().into_bytes(), true, false),
|
||||
["rule", "add"] | ["rule", "del"] => (Vec::new(), false, true),
|
||||
["nat", "list"] => (self.table.borrow().nat_table.format().into_bytes(), true, false),
|
||||
["nat", "add"] | ["nat", "del"] => (Vec::new(), false, true),
|
||||
["conntrack", "list"] => (
|
||||
self.table.borrow().conntrack.as_ref()
|
||||
.map(|ct| ct.format().into_bytes())
|
||||
.unwrap_or_else(|| b"conntrack: not enabled\n".to_vec()),
|
||||
true, false,
|
||||
),
|
||||
["log"] => {
|
||||
let buf = &self.table.borrow().log_buffer;
|
||||
let mut out = String::new();
|
||||
for entry in buf.iter().rev().take(20) {
|
||||
out.push_str(entry);
|
||||
out.push('\n');
|
||||
}
|
||||
(out.into_bytes(), true, false)
|
||||
},
|
||||
["policy", name] => {
|
||||
let hook = parse_hook_name(name)
|
||||
.ok_or_else(|| SyscallError::new(syscall::ENOENT))?;
|
||||
let policy = self
|
||||
.table
|
||||
.borrow()
|
||||
.default_policy
|
||||
.get(&hook)
|
||||
.copied()
|
||||
.unwrap_or(Verdict::Accept);
|
||||
(format!("{}\n", policy.name()).into_bytes(), true, true)
|
||||
}
|
||||
["reset"] => (Vec::new(), false, true),
|
||||
_ => return Err(SyscallError::new(syscall::ENOENT)),
|
||||
};
|
||||
|
||||
let fd = self.handles.insert(Handle::File(NetFilterFile {
|
||||
path: path.to_string(),
|
||||
is_dir,
|
||||
is_writable,
|
||||
is_readable,
|
||||
read_buf,
|
||||
write_buf: Vec::new(),
|
||||
pos: 0,
|
||||
done: false,
|
||||
}));
|
||||
Ok(fd)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hook_name(name: &str) -> Option<Hook> {
|
||||
match name.to_lowercase().as_str() {
|
||||
"prerouting" => Some(Hook::PreRouting),
|
||||
"input" => Some(Hook::InputLocal),
|
||||
"forward" => Some(Hook::Forward),
|
||||
"output" => Some(Hook::OutputLocal),
|
||||
"postrouting" => Some(Hook::PostRouting),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl NetFilterFile {
|
||||
fn commit(&mut self, table: &FilterTableRef) -> SyscallResult<()> {
|
||||
if self.write_buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let line = str::from_utf8(&self.write_buf)
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?
|
||||
.trim();
|
||||
|
||||
let path = self.path.clone();
|
||||
let mut table = table.borrow_mut();
|
||||
|
||||
if path == "/rule/add" {
|
||||
let rule = parse_rule(line)
|
||||
.map_err(|e| SyscallError::new(syscall::EINVAL))?;
|
||||
let id = table.add(rule);
|
||||
log::info!("netfilter: added rule id={}", id);
|
||||
} else if path == "/nat/add" {
|
||||
let nat_rule = parse_nat_rule(line)
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
let id = table.nat_table.add(nat_rule);
|
||||
log::info!("netfilter: added NAT rule id={}", id);
|
||||
} else if path == "/nat/del" {
|
||||
let id: u32 = line
|
||||
.parse()
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if !table.nat_table.remove(id) {
|
||||
return Err(SyscallError::new(syscall::ENOENT));
|
||||
}
|
||||
log::info!("netfilter: removed NAT rule id={}", id);
|
||||
} else if let Some(rest) = path.strip_prefix("/rule/del/") {
|
||||
let id: u32 = rest
|
||||
.parse()
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if !table.remove(id) {
|
||||
return Err(SyscallError::new(syscall::ENOENT));
|
||||
}
|
||||
log::info!("netfilter: removed rule id={}", id);
|
||||
} else if path == "/reset" {
|
||||
*table = FilterTable::new();
|
||||
log::info!("netfilter: table reset to defaults");
|
||||
} else if let Some(rest) = path.strip_prefix("/policy/") {
|
||||
let hook = parse_hook_name(rest).ok_or(SyscallError::new(syscall::EINVAL))?;
|
||||
let verdict = match line.to_uppercase().as_str() {
|
||||
"ACCEPT" => Verdict::Accept,
|
||||
"DROP" => Verdict::Drop,
|
||||
_ => return Err(SyscallError::new(syscall::EINVAL)),
|
||||
};
|
||||
table.set_default_policy(hook, verdict);
|
||||
log::info!("netfilter: set {} policy to {}", rest, verdict.name());
|
||||
} else if path == "/rule/del" {
|
||||
let id: u32 = line
|
||||
.parse()
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if !table.remove(id) {
|
||||
return Err(SyscallError::new(syscall::ENOENT));
|
||||
}
|
||||
log::info!("netfilter: removed rule id={}", id);
|
||||
} else {
|
||||
return Err(SyscallError::new(syscall::EINVAL));
|
||||
}
|
||||
self.write_buf.clear();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for NetFilterSchemeInner {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.uid != 0 {
|
||||
return Err(SyscallError::new(syscall::EACCES));
|
||||
}
|
||||
|
||||
let fd = self.open_path(path)?;
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: fd,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_close(&mut self, fd: usize) {
|
||||
self.on_close(fd);
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
fd: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> SyscallResult<usize> {
|
||||
if ctx.uid != 0 {
|
||||
return Err(SyscallError::new(syscall::EACCES));
|
||||
}
|
||||
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 (WRITE_BUFFER_MAX_SIZE - file.write_buf.len()) < buf.len() {
|
||||
return Err(SyscallError::new(syscall::EMSGSIZE));
|
||||
}
|
||||
file.write_buf.extend_from_slice(buf);
|
||||
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 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 {
|
||||
file.done = true;
|
||||
file.commit(&self.table)
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EBADF))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,17 +251,24 @@ where
|
||||
flags: usize,
|
||||
) -> SyscallResult<usize>;
|
||||
|
||||
fn get_sock_opt(
|
||||
fn get_sock_opt(
|
||||
&self,
|
||||
file: &SchemeFile<Self>,
|
||||
name: usize,
|
||||
buf: &mut [u8],
|
||||
) -> SyscallResult<usize> {
|
||||
// Return Err for default implementation
|
||||
Err(SyscallError::new(syscall::ENOPROTOOPT))
|
||||
}
|
||||
|
||||
fn set_sock_opt(
|
||||
&mut self,
|
||||
_file: &SchemeFile<Self>,
|
||||
_name: usize,
|
||||
_buf: &[u8],
|
||||
) -> SyscallResult<usize> {
|
||||
Err(SyscallError::new(syscall::ENOPROTOOPT))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Handle<SocketT>
|
||||
where
|
||||
SocketT: SchemeSocket,
|
||||
@@ -453,10 +460,20 @@ where
|
||||
// SocketCall::Bind => self.handle_bind(id, &payload),
|
||||
// SocketCall::Connect => self.handle_connect(id, &payload),
|
||||
SocketCall::SetSockOpt => {
|
||||
// currently not used
|
||||
// self.handle_setsockopt(id, metadata[1] as i32, &payload)
|
||||
// TODO: SO_REUSEADDR from null socket
|
||||
Ok(0)
|
||||
let handle = self.handles.get(fd)?;
|
||||
match handle {
|
||||
Handle::File(file) => {
|
||||
let mut socket_set = self.socket_set.borrow_mut();
|
||||
let socket = socket_set.get_mut::<SocketT>(file.socket_handle());
|
||||
SocketT::set_sock_opt(
|
||||
socket,
|
||||
file,
|
||||
metadata[1] as usize,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
|
||||
}
|
||||
}
|
||||
SocketCall::GetSockOpt => {
|
||||
let handle = self.handles.get_mut(fd)?;
|
||||
|
||||
+162
-1
@@ -1,6 +1,7 @@
|
||||
use scheme_utils::FpathWriter;
|
||||
use smoltcp::iface::SocketHandle;
|
||||
use smoltcp::socket::tcp::{Socket as TcpSocket, SocketBuffer as TcpSocketBuffer};
|
||||
use smoltcp::time::Duration;
|
||||
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
|
||||
use std::str;
|
||||
use syscall;
|
||||
@@ -13,6 +14,17 @@ use libredox::flag;
|
||||
|
||||
const SO_SNDBUF: usize = 7;
|
||||
const SO_RCVBUF: usize = 8;
|
||||
const SO_KEEPALIVE: usize = 9;
|
||||
const TCP_NODELAY: usize = 1;
|
||||
const TCP_MAXSEG: usize = 2;
|
||||
const TCP_KEEPIDLE: usize = 4;
|
||||
const TCP_KEEPINTVL: usize = 5;
|
||||
const TCP_KEEPCNT: usize = 6;
|
||||
const TCP_INFO: usize = 11;
|
||||
const TCP_QUICKACK: usize = 12;
|
||||
const TCP_CONGESTION: usize = 13;
|
||||
const IP_TTL: usize = 2;
|
||||
const IP_MTU_DISCOVER: usize = 10;
|
||||
|
||||
pub type TcpScheme = SchemeWrapper<TcpSocket<'static>>;
|
||||
|
||||
@@ -88,7 +100,9 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
|
||||
local_endpoint.port = port_set
|
||||
.get_port()
|
||||
.ok_or_else(|| SyscallError::new(syscall::EINVAL))?;
|
||||
} else if !port_set.claim_port(local_endpoint.port) {
|
||||
} else if !port_set.claim_port(local_endpoint.port)
|
||||
&& !port_set.claim_port_reuse(local_endpoint.port)
|
||||
{
|
||||
return Err(SyscallError::new(syscall::EADDRINUSE));
|
||||
}
|
||||
|
||||
@@ -403,6 +417,13 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
|
||||
buf: &mut [u8],
|
||||
) -> SyscallResult<usize> {
|
||||
match name {
|
||||
SO_KEEPALIVE => {
|
||||
let val: u32 = if self.keep_alive().is_some() { 1 } else { 0 };
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
SO_RCVBUF => {
|
||||
let val = self.recv_capacity() as i32;
|
||||
let bytes = val.to_ne_bytes();
|
||||
@@ -425,7 +446,147 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
|
||||
buf[0..bytes.len()].copy_from_slice(&bytes);
|
||||
Ok(bytes.len())
|
||||
}
|
||||
TCP_NODELAY => {
|
||||
let val: u32 = if self.nagle_enabled() { 0 } else { 1 };
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_KEEPIDLE => {
|
||||
let secs = self.keep_alive().map(|d| d.secs()).unwrap_or(0);
|
||||
let val: u32 = secs as u32;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_KEEPINTVL => {
|
||||
let val: u32 = 75;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_KEEPCNT => {
|
||||
let val: u32 = 9;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_INFO => {
|
||||
let info = TcpInfo {
|
||||
tcpi_state: self.state() as u8,
|
||||
_pad: [0; 3],
|
||||
tcpi_snd_queuelen: self.send_queue() as u32,
|
||||
tcpi_rto: 3000,
|
||||
};
|
||||
let bytes = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
&info as *const TcpInfo as *const u8,
|
||||
core::mem::size_of::<TcpInfo>(),
|
||||
)
|
||||
};
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_MAXSEG => {
|
||||
let val: u32 = 1460;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_QUICKACK => {
|
||||
let val: u32 = 1;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
TCP_CONGESTION => {
|
||||
let name = b"cubic";
|
||||
let len = buf.len().min(name.len());
|
||||
buf[..len].copy_from_slice(&name[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
IP_TTL => {
|
||||
let val = self.hop_limit().unwrap_or(64) as u32;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
IP_MTU_DISCOVER => {
|
||||
let val: u32 = 1;
|
||||
let bytes = val.to_ne_bytes();
|
||||
let len = buf.len().min(bytes.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_sock_opt(
|
||||
&mut self,
|
||||
_file: &SchemeFile<Self>,
|
||||
name: usize,
|
||||
buf: &[u8],
|
||||
) -> SyscallResult<usize> {
|
||||
match name {
|
||||
SO_KEEPALIVE => {
|
||||
let enabled = buf.first().copied().unwrap_or(0) != 0;
|
||||
self.set_keep_alive(if enabled {
|
||||
Some(Duration::from_secs(7200))
|
||||
} else {
|
||||
None
|
||||
});
|
||||
Ok(1)
|
||||
}
|
||||
TCP_NODELAY => {
|
||||
let enabled = buf.first().copied().unwrap_or(0) != 0;
|
||||
self.set_nagle_enabled(!enabled);
|
||||
Ok(1)
|
||||
}
|
||||
TCP_KEEPIDLE => {
|
||||
let val = buf.first().copied().unwrap_or(0) as u64;
|
||||
if val > 0 {
|
||||
self.set_keep_alive(Some(Duration::from_secs(val)));
|
||||
} else {
|
||||
self.set_keep_alive(None);
|
||||
}
|
||||
Ok(1)
|
||||
}
|
||||
TCP_QUICKACK => {
|
||||
let enabled = buf.first().copied().unwrap_or(0) != 0;
|
||||
self.set_ack_delay(if enabled { None } else { Some(Duration::from_millis(100)) });
|
||||
Ok(1)
|
||||
}
|
||||
TCP_CONGESTION => {
|
||||
let name = std::str::from_utf8(buf).map_err(|_| SyscallError::new(syscall::EINVAL))?;
|
||||
if name.trim_end_matches('\0') != "cubic" {
|
||||
return Err(SyscallError::new(syscall::EOPNOTSUPP));
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
IP_TTL => {
|
||||
let val = buf.first().copied().unwrap_or(64);
|
||||
self.set_hop_limit(Some(val));
|
||||
Ok(1)
|
||||
}
|
||||
IP_MTU_DISCOVER => Ok(1),
|
||||
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct TcpInfo {
|
||||
tcpi_state: u8,
|
||||
_pad: [u8; 3],
|
||||
tcpi_snd_queuelen: u32,
|
||||
tcpi_rto: u32,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
//! TUN scheme — userspace virtual network device interface.
|
||||
//!
|
||||
//! Mirrors Linux 7.1's `/dev/net/tun` character device:
|
||||
//! - `TUNSETIFF` ioctl creates a tun device
|
||||
//! - `read()` → `tun_put_user()` (kernel→userspace packet)
|
||||
//! - `write()` → `tun_get_user()` (userspace→kernel packet)
|
||||
//!
|
||||
//! In Red Bear, this is exposed as a scheme:
|
||||
//! ```text
|
||||
//! /scheme/tun/
|
||||
//! ├── create — write: device name → creates TUN device, returns fd
|
||||
//! └── <name>/
|
||||
//! ├── data — read/write: raw IP frames
|
||||
//! └── mtu — read: return MTU (1500)
|
||||
//! ```
|
||||
|
||||
use redox_scheme::{
|
||||
scheme::{register_scheme_inner, SchemeState, SchemeSync},
|
||||
CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket,
|
||||
};
|
||||
use scheme_utils::HandleMap;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::rc::Rc;
|
||||
use syscall;
|
||||
use syscall::data::Stat;
|
||||
use syscall::flag::{MODE_DIR, MODE_FILE};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error as SyscallError, Result as SyscallResult};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::link::tun::{PacketQueue, TunDevice};
|
||||
use crate::link::DeviceList;
|
||||
|
||||
type DeviceListRef = Rc<RefCell<DeviceList>>;
|
||||
|
||||
pub struct TunScheme {
|
||||
inner: TunSchemeInner,
|
||||
state: SchemeState,
|
||||
}
|
||||
|
||||
struct TunDeviceState {
|
||||
rx: PacketQueue,
|
||||
tx: PacketQueue,
|
||||
}
|
||||
|
||||
struct TunFile {
|
||||
path: String,
|
||||
is_dir: bool,
|
||||
read_buf: Vec<u8>,
|
||||
write_buf: Vec<u8>,
|
||||
pos: usize,
|
||||
device_rx: Option<PacketQueue>,
|
||||
device_tx: Option<PacketQueue>,
|
||||
}
|
||||
|
||||
struct TunSchemeInner {
|
||||
scheme_file: Socket,
|
||||
handles: HandleMap<TunFile>,
|
||||
devices: BTreeMap<String, TunDeviceState>,
|
||||
device_list: DeviceListRef,
|
||||
}
|
||||
|
||||
impl TunScheme {
|
||||
pub fn new(scheme_file: Socket, device_list: DeviceListRef) -> Result<TunScheme> {
|
||||
let mut inner = TunSchemeInner {
|
||||
scheme_file,
|
||||
handles: HandleMap::new(),
|
||||
devices: BTreeMap::new(),
|
||||
device_list,
|
||||
};
|
||||
let cap_id = inner
|
||||
.scheme_root()
|
||||
.map_err(|e| Error::from_syscall_error(e, "failed to get tun scheme root id"))?;
|
||||
register_scheme_inner(&inner.scheme_file, "tun", cap_id).map_err(|e| {
|
||||
Error::from_syscall_error(e, "failed to register tun scheme to namespace")
|
||||
})?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
state: SchemeState::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_scheme_event(&mut self) -> Result<Option<()>> {
|
||||
loop {
|
||||
let request = match self.inner.scheme_file.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(req)) => req,
|
||||
Ok(None) => return Ok(Some(())),
|
||||
Err(error)
|
||||
if error.errno == syscall::EWOULDBLOCK || error.errno == syscall::EAGAIN =>
|
||||
{
|
||||
break;
|
||||
}
|
||||
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.handles.remove(id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for (name, dev) in &self.inner.devices {
|
||||
let mut written = Vec::new();
|
||||
{
|
||||
let mut rx = dev.tx.borrow_mut();
|
||||
while let Some(packet) = rx.pop_front() {
|
||||
written.push(packet);
|
||||
}
|
||||
}
|
||||
if !written.is_empty() {
|
||||
let mut rx = dev.rx.borrow_mut();
|
||||
for packet in written {
|
||||
rx.push_back(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for TunSchemeInner {
|
||||
fn scheme_root(&mut self) -> SyscallResult<usize> {
|
||||
Ok(self.handles.insert(TunFile {
|
||||
path: String::new(),
|
||||
is_dir: true,
|
||||
read_buf: Vec::new(),
|
||||
write_buf: Vec::new(),
|
||||
pos: 0,
|
||||
device_rx: None,
|
||||
device_tx: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> SyscallResult<OpenResult> {
|
||||
let dir = self.handles.get(dirfd)?;
|
||||
if !dir.is_dir {
|
||||
return Err(SyscallError::new(syscall::EACCES));
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = path.trim_matches('/').split('/').filter(|p| !p.is_empty()).collect();
|
||||
|
||||
match parts.as_slice() {
|
||||
["create"] => {
|
||||
let fd = self.handles.insert(TunFile {
|
||||
path: "create".to_string(),
|
||||
is_dir: false,
|
||||
read_buf: Vec::new(),
|
||||
write_buf: Vec::new(),
|
||||
pos: 0,
|
||||
device_rx: None,
|
||||
device_tx: None,
|
||||
});
|
||||
Ok(OpenResult::ThisScheme { number: fd, flags: NewFdFlags::empty() })
|
||||
}
|
||||
[name, "data"] if self.devices.contains_key(*name) => {
|
||||
let dev = &self.devices[*name];
|
||||
let fd = self.handles.insert(TunFile {
|
||||
path: format!("{}/data", name),
|
||||
is_dir: false,
|
||||
read_buf: Vec::new(),
|
||||
write_buf: Vec::new(),
|
||||
pos: 0,
|
||||
device_rx: Some(Rc::clone(&dev.tx)),
|
||||
device_tx: Some(Rc::clone(&dev.rx)),
|
||||
});
|
||||
Ok(OpenResult::ThisScheme { number: fd, flags: NewFdFlags::empty() })
|
||||
}
|
||||
_ => Err(SyscallError::new(syscall::ENOENT)),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
fd: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> SyscallResult<usize> {
|
||||
let file = self.handles.get_mut(fd)?;
|
||||
|
||||
if file.path == "create" {
|
||||
let name = std::str::from_utf8(buf)
|
||||
.map_err(|_| SyscallError::new(syscall::EINVAL))?
|
||||
.trim()
|
||||
.to_string();
|
||||
if name.is_empty() || self.devices.contains_key(&name) {
|
||||
return Err(SyscallError::new(syscall::EEXIST));
|
||||
}
|
||||
let rx: PacketQueue = Rc::new(RefCell::new(VecDeque::new()));
|
||||
let tx: PacketQueue = Rc::new(RefCell::new(VecDeque::new()));
|
||||
let tun_dev = TunDevice::new(&name, Rc::clone(&rx), Rc::clone(&tx));
|
||||
self.device_list.borrow_mut().push(tun_dev);
|
||||
self.devices.insert(name.clone(), TunDeviceState { rx, tx });
|
||||
log::info!("tun: created device {}", name);
|
||||
Ok(buf.len())
|
||||
} else if let Some(rx) = &file.device_tx {
|
||||
let packet = buf.to_vec();
|
||||
rx.borrow_mut().push_back(packet);
|
||||
Ok(buf.len())
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
fd: usize,
|
||||
buf: &mut [u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> SyscallResult<usize> {
|
||||
let file = self.handles.get_mut(fd)?;
|
||||
|
||||
if let Some(rx) = &file.device_rx {
|
||||
if file.pos < file.read_buf.len() {
|
||||
let remaining = file.read_buf.len() - file.pos;
|
||||
let to_copy = buf.len().min(remaining);
|
||||
buf[..to_copy].copy_from_slice(&file.read_buf[file.pos..file.pos + to_copy]);
|
||||
file.pos += to_copy;
|
||||
return Ok(to_copy);
|
||||
}
|
||||
file.read_buf.clear();
|
||||
file.pos = 0;
|
||||
if let Some(packet) = rx.borrow_mut().pop_front() {
|
||||
let to_copy = buf.len().min(packet.len());
|
||||
buf[..to_copy].copy_from_slice(&packet[..to_copy]);
|
||||
if to_copy < packet.len() {
|
||||
file.read_buf = packet[to_copy..].to_vec();
|
||||
}
|
||||
return Ok(to_copy);
|
||||
}
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(SyscallError::new(syscall::EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn fstat(&mut self, fd: usize, stat: &mut Stat, _ctx: &CallerCtx) -> SyscallResult<()> {
|
||||
let file = self.handles.get_mut(fd)?;
|
||||
stat.st_mode = if file.is_dir { MODE_DIR } else { MODE_FILE };
|
||||
stat.st_uid = 0;
|
||||
stat.st_gid = 0;
|
||||
stat.st_size = 0;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user