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:
Red Bear OS
2026-07-08 13:27:49 +03:00
parent 4506bfe02a
commit bb3e36e4e0
40 changed files with 6042 additions and 467 deletions
+40 -1
View File
@@ -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) {
+141 -5
View File
@@ -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,
+408
View 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))
}
}
}
+24 -7
View File
@@ -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
View File
@@ -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,
}
+272
View File
@@ -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(())
}
}