Files
RedBear-OS/netstack/src/scheme/mod.rs
T
Red Bear OS ba5a7cd3fc sysctl: add /scheme/netcfg/sysctl tree with ip_forward toggle
Router gains Rc<Cell<bool>> ip_forward flag (default: true).
When false, forward_packets() returns immediately — no packets forwarded
between interfaces. Security best practice for non-router hosts.

netcfg scheme gains sysctl subtree:
  /scheme/netcfg/sysctl/net/ipv4/ip_forward  (rw: 0 or 1)

Read:  echo /scheme/netcfg/sysctl/net/ipv4/ip_forward  →  1
Write: echo 0 > /scheme/netcfg/sysctl/net/ipv4/ip_forward  (disable)
Restore: echo 1 > /scheme/netcfg/sysctl/net/ipv4/ip_forward

Mirrors Linux /proc/sys/net/ipv4/ip_forward.
Shared via Rc<Cell<bool>> between Router and NetCfgScheme.
2026-07-08 15:20:21 +03:00

611 lines
21 KiB
Rust

use crate::link::ethernet::EthernetLink;
use crate::link::LinkDevice;
use crate::link::{loopback::LoopbackDevice, DeviceList};
use crate::router::route_table::{RouteTable, Rule};
use crate::router::Router;
use crate::scheme::smoltcp::iface::SocketSet as SmoltcpSocketSet;
use crate::scheme::socket::{Handle, SchemeSocket, SocketScheme};
use libredox::flag;
use libredox::Fd;
use redox_scheme::{
scheme::{IntoTag, Op, SchemeResponse, SchemeState, SchemeSync},
CallerCtx, RequestKind, Response, SignalBehavior, Socket,
};
use smoltcp;
use smoltcp::iface::{Config, Interface as SmoltcpInterface};
use smoltcp::phy::Tracer;
use smoltcp::socket::AnySocket;
use smoltcp::time::{Duration, Instant};
use smoltcp::wire::{
EthernetAddress, HardwareAddress, IpAddress, IpCidr, IpListenEndpoint, Ipv4Address,
};
use std::cell::RefCell;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::size_of;
use std::os::fd::{FromRawFd, RawFd};
use std::rc::Rc;
use std::str::FromStr;
use syscall;
use syscall::data::TimeSpec;
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);
fn getcfg(key: &str) -> Result<String> {
let mut value = String::new();
let mut file = File::open(format!("/etc/net/{key}"))?;
file.read_to_string(&mut value)?;
Ok(value.trim().to_string())
}
pub struct Smolnetd {
router_device: Tracer<Router>,
iface: Interface,
time_file: File,
socket_set: Rc<RefCell<SocketSet>>,
timer: ::std::time::Instant,
ip_scheme: IpScheme,
udp_scheme: UdpScheme,
tcp_scheme: TcpScheme,
icmp_scheme: IcmpScheme,
netcfg_scheme: NetCfgScheme,
netfilter_scheme: NetFilterScheme,
tun_scheme: TunScheme,
filter_table: FilterTableRef,
}
impl Smolnetd {
pub const MAX_PACKET_SIZE: usize = 2048;
pub const SOCKET_BUFFER_SIZE: usize = 128; //packets
pub const MIN_CHECK_TIMEOUT: Duration = Duration::from_millis(10);
pub const MAX_CHECK_TIMEOUT: Duration = Duration::from_millis(500);
pub fn new(
network_file: Fd,
hardware_addr: EthernetAddress,
ip_file: Socket,
udp_file: Socket,
tcp_file: Socket,
icmp_file: Socket,
time_file: Fd,
netcfg_file: Socket,
netfilter_file: Socket,
tun_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())
.expect("Can't parse the 'ip_router' cfg.");
let devices = Rc::new(RefCell::new(DeviceList::default()));
let route_table = Rc::new(RefCell::new(RouteTable::default()));
let mut network_device = Tracer::new(
Router::new(Rc::clone(&devices), Rc::clone(&route_table)),
|_timestamp, printer| trace!("{}", printer),
);
let ip_forward = network_device.get_mut().ip_forward.clone();
let config = Config::new(HardwareAddress::Ip);
let mut iface = SmoltcpInterface::new(config, &mut network_device, Instant::now());
iface.update_ip_addrs(|ip_addrs| ip_addrs.extend(protocol_addrs));
iface
.routes_mut()
.add_default_ipv4_route(default_gw)
.expect("Failed to add default gateway");
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(
"127.0.0.0/8".parse().unwrap(),
None,
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)
});
eth0.set_mac_address(hardware_addr);
devices.borrow_mut().push(loopback);
devices.borrow_mut().push(eth0);
Ok(Smolnetd {
iface: Rc::clone(&iface),
router_device: network_device,
socket_set: Rc::clone(&socket_set),
timer: ::std::time::Instant::now(),
time_file: unsafe { File::from_raw_fd(time_file.into_raw() as RawFd) },
ip_scheme: IpScheme::new(
"ip",
Rc::clone(&iface),
Rc::clone(&route_table),
Rc::clone(&socket_set),
ip_file,
)?,
udp_scheme: UdpScheme::new(
"udp",
Rc::clone(&iface),
Rc::clone(&route_table),
Rc::clone(&socket_set),
udp_file,
)?,
tcp_scheme: TcpScheme::new(
"tcp",
Rc::clone(&iface),
Rc::clone(&route_table),
Rc::clone(&socket_set),
tcp_file,
)?,
icmp_scheme: IcmpScheme::new(
"icmp",
Rc::clone(&iface),
Rc::clone(&route_table),
Rc::clone(&socket_set),
icmp_file,
)?,
netcfg_scheme: NetCfgScheme::new(
Rc::clone(&iface),
netcfg_file,
Rc::clone(&route_table),
Rc::clone(&devices),
Rc::clone(&socket_set),
ip_forward,
)?,
netfilter_scheme: NetFilterScheme::new(
netfilter_file,
Rc::clone(&filter_table),
)?,
tun_scheme: TunScheme::new(tun_file, Rc::clone(&devices))?,
filter_table,
})
}
pub fn on_network_scheme_event(&mut self) -> Result<()> {
self.poll()?;
Ok(())
}
pub fn on_ip_scheme_event(&mut self) -> Result<()> {
self.ip_scheme.on_scheme_event()?;
let _ = self.poll()?;
Ok(())
}
pub fn on_udp_scheme_event(&mut self) -> Result<()> {
self.udp_scheme.on_scheme_event()?;
let _ = self.poll()?;
Ok(())
}
pub fn on_tcp_scheme_event(&mut self) -> Result<()> {
self.tcp_scheme.on_scheme_event()?;
let _ = self.poll()?;
Ok(())
}
pub fn on_icmp_scheme_event(&mut self) -> Result<()> {
self.icmp_scheme.on_scheme_event()?;
let _ = self.poll()?;
Ok(())
}
pub fn on_time_event(&mut self) -> Result<()> {
let timeout = self.poll()?;
self.schedule_time_event(timeout)?;
//TODO: Fix network scheme to ensure events are not missed
self.on_network_scheme_event()
}
pub fn on_netcfg_scheme_event(&mut self) -> Result<()> {
self.netcfg_scheme.on_scheme_event()?;
Ok(())
}
pub fn on_netfilter_scheme_event(&mut self) -> Result<()> {
self.netfilter_scheme.on_scheme_event()?;
Ok(())
}
pub fn on_tun_scheme_event(&mut self) -> Result<()> {
self.tun_scheme.on_scheme_event()?;
let _ = self.poll()?;
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>() {
return Err(Error::from_syscall_error(
syscall::Error::new(syscall::EBADF),
"Can't read current time",
));
}
let mut time_ms = time.tv_sec * 1000i64 + i64::from(time.tv_nsec) / 1_000_000i64;
time_ms += timeout.total_millis() as i64;
time.tv_sec = time_ms / 1000;
time.tv_nsec = ((time_ms % 1000) * 1_000_000) as i32;
self.time_file
.write_all(&time)
.map_err(|e| Error::from_io_error(e, "Failed to write to time file"))?;
Ok(())
}
fn poll(&mut self) -> Result<Duration> {
let timeout = {
let mut iter_limit = 10usize;
let mut iface = self.iface.borrow_mut();
let mut socket_set = self.socket_set.borrow_mut();
loop {
let timestamp = Instant::from(self.timer);
if iter_limit == 0 {
break MIN_DURATION;
}
iter_limit -= 1;
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.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) {
Some(delay) if delay == Duration::ZERO => {}
Some(delay) => break ::std::cmp::min(MAX_DURATION, delay),
None => break MAX_DURATION,
};
}
}
};
self.notify_sockets()?;
Ok(::std::cmp::min(
::std::cmp::max(Smolnetd::MIN_CHECK_TIMEOUT, timeout),
Smolnetd::MAX_CHECK_TIMEOUT,
))
}
fn notify_sockets(&mut self) -> Result<()> {
self.ip_scheme.notify_sockets()?;
self.udp_scheme.notify_sockets()?;
self.tcp_scheme.notify_sockets()?;
self.icmp_scheme.notify_sockets()
}
}
fn post_fevent(socket: &Socket, id: usize, flags: usize) -> syscall::error::Result<()> {
let fevent_response = Response::post_fevent(id, flags);
match socket.write_response(fevent_response, SignalBehavior::Restart) {
Ok(true) => Ok(()), // Write response success
Ok(false) => Err(syscall::error::Error::new(syscall::EAGAIN)), // Write response failed, retry.
Err(err) => Err(err), // Error writing response
}
}
fn parse_endpoint(socket: &str) -> IpListenEndpoint {
let mut socket_parts = socket.split(':');
let host = Ipv4Address::from_str(socket_parts.next().unwrap_or(""))
.ok()
.filter(|addr| !addr.is_unspecified())
.map(IpAddress::Ipv4);
let port = socket_parts
.next()
.unwrap_or("")
.parse::<u16>()
.unwrap_or(0);
IpListenEndpoint { addr: host, port }
}
struct WaitHandle {
until: Option<TimeSpec>,
cancelling: bool,
packet: (Op, CallerCtx),
}
type WaitQueue = Vec<WaitHandle>;
pub struct SchemeWrapper<SocketT>
where
SocketT: SchemeSocket + AnySocket<'static>,
{
scheme: socket::SocketScheme<SocketT>,
state: SchemeState,
wait_queue: WaitQueue,
}
impl<SocketT> SchemeWrapper<SocketT>
where
SocketT: SchemeSocket + AnySocket<'static>,
{
pub fn new(
name: &str,
iface: Interface,
route_table: Rc<RefCell<RouteTable>>,
socket_set: Rc<RefCell<SocketSet>>,
scheme_file: Socket,
) -> Result<Self> {
Ok(Self {
scheme: SocketScheme::<SocketT>::new(name, iface, route_table, socket_set, scheme_file)
.map_err(|e| {
Error::from_syscall_error(e, &format!("failed to initialize {} scheme", name))
})?,
state: SchemeState::new(),
wait_queue: Vec::new(),
})
}
pub fn on_scheme_event(&mut self) -> Result<Option<()>> {
let result = loop {
let request = match self
.scheme
.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",
))
}
};
let req = match request.kind() {
RequestKind::Call(c) => c,
RequestKind::OnClose { id } => {
self.scheme.on_close(id);
continue;
}
RequestKind::Cancellation(req) => {
if let Some(idx) = self
.wait_queue
.iter()
.position(|q| q.packet.0.req_id() == req.id)
{
self.wait_queue[idx].cancelling = true;
}
continue;
}
_ => {
continue;
}
};
let caller = req.caller();
let mut op = match req.op() {
Ok(op) => op,
Err(req) => {
self.scheme
.scheme_file
.write_response(
Response::err(syscall::EOPNOTSUPP, req),
SignalBehavior::Restart,
)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
continue;
}
};
let resp = match op.handle_sync_dont_consume(&caller, &mut self.scheme, &mut self.state)
{
SchemeResponse::Opened(Err(SyscallError {
errno: syscall::EWOULDBLOCK,
}))
| SchemeResponse::Regular(Err(SyscallError {
errno: syscall::EWOULDBLOCK,
})) if !op.is_explicitly_nonblock() => {
match self.scheme.handle_block(&op) {
Ok(timeout) => {
self.wait_queue.push(WaitHandle {
until: timeout,
cancelling: false,
packet: (op, caller),
});
}
Err(err) => {
let _ = self
.scheme
.scheme_file
.write_response(
Response::err(err.errno, op),
SignalBehavior::Restart,
)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
return Err(Error::from_syscall_error(
err,
"Can't handle blocked socket",
));
}
}
continue;
}
SchemeResponse::Regular(r) => Response::new(r, op),
SchemeResponse::Opened(o) => Response::open_dup_like(o, op),
SchemeResponse::RegularAndNotifyOnDetach(status) => {
Response::new_notify_on_detach(status, op)
}
};
let _ = self
.scheme
.scheme_file
.write_response(resp, SignalBehavior::Restart)
.map_err(|e| Error::from_syscall_error(e.into(), "failed to write response"))?;
};
Ok(result)
}
pub fn notify_sockets(&mut self) -> Result<()> {
let cur_time = libredox::call::clock_gettime(flag::CLOCK_MONOTONIC)
.map_err(|e| Error::from_syscall_error(e.into(), "Can't get time"))?;
// Notify non-blocking sockets
let scheme = &mut self.scheme;
let state = &mut self.state;
for (&fd, handle) in scheme.handles.iter_mut() {
let Handle::File(file) = handle else {
continue;
};
let events = {
let mut socket_set = scheme.socket_set.borrow_mut();
file.events(&mut socket_set)
};
if events > 0 {
post_fevent(&scheme.scheme_file, fd, events)
.map_err(|e| Error::from_syscall_error(e.into(), "failed to post fevent"))?;
}
}
// Wake up blocking queue
let queue = &mut self.wait_queue;
let mut i = 0;
while i < queue.len() {
let handle = &mut queue[i];
let (op, caller) = &mut handle.packet;
let res = op.handle_sync_dont_consume(caller, scheme, state);
match res {
SchemeResponse::Opened(Err(SyscallError {
errno: syscall::EWOULDBLOCK,
}))
| SchemeResponse::Regular(Err(SyscallError {
errno: syscall::EWOULDBLOCK,
})) if !op.is_explicitly_nonblock() => {
if handle.cancelling {
let (op, _) = queue.swap_remove(i).packet;
scheme
.scheme_file
.write_response(
Response::err(syscall::ECANCELED, op),
SignalBehavior::Restart,
)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
continue;
}
match handle.until {
Some(until)
if (until.tv_sec < cur_time.tv_sec
|| (until.tv_sec == cur_time.tv_sec
&& i64::from(until.tv_nsec) < i64::from(cur_time.tv_nsec))) =>
{
let (op, _) = queue.swap_remove(i).packet;
let _ = scheme
.scheme_file
.write_response(
Response::err(syscall::ETIMEDOUT, op),
SignalBehavior::Restart,
)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
}
_ => {
i += 1;
}
}
}
SchemeResponse::Regular(r) => {
let (op, _) = queue.swap_remove(i).packet;
let _ = scheme
.scheme_file
.write_response(Response::new(r, op), SignalBehavior::Restart)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
}
SchemeResponse::Opened(o) => {
let (op, _) = queue.swap_remove(i).packet;
let _ = scheme
.scheme_file
.write_response(Response::open_dup_like(o, op), SignalBehavior::Restart)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
}
SchemeResponse::RegularAndNotifyOnDetach(status) => {
let (op, _) = queue.swap_remove(i).packet;
let _ = scheme
.scheme_file
.write_response(
Response::new_notify_on_detach(status, op),
SignalBehavior::Restart,
)
.map_err(|e| {
Error::from_syscall_error(e.into(), "failed to write response")
})?;
}
}
}
Ok(())
}
}