base: apply Red Bear patches on latest upstream/main

251 files: init, acpid, ipcd, netcfg, ihdgd, virtio-gpud, scheme-utils,
inputd, block driver, ptyd, ramfs, randd, initfs bootstrap, path deps,
version +rb0.3.1, author attribution
This commit is contained in:
Red Bear OS
2026-07-11 11:39:24 +03:00
parent 1b17b3fc24
commit bd595851e2
251 changed files with 24641 additions and 5993 deletions
+53 -14
View File
@@ -17,6 +17,16 @@ use crate::router::Router;
pub type IcmpScheme = SchemeWrapper<IcmpSocket<'static>>;
// ICMP socket variants.
//
// Echo: standard ICMP echo request/reply (e.g. for ping). Ident is
// assigned from the ICMP ident pool (1..0xffff).
//
// Udp: notification endpoint for ICMP error messages received for
// connected UDP sockets. Mirrors Linux's `IP_RECVERR` mechanism. When
// the network receives an ICMPv4 error for a UDP datagram, the matching
// socket is woken with `EWOULDBLOCK` and the error is queued. The user
// reads it via the ICMP scheme's read path. This path is read-only.
enum IcmpSocketType {
Echo,
Udp,
@@ -214,22 +224,51 @@ impl<'a> SchemeSocket for IcmpSocket<'a> {
return Ok(0);
}
while self.can_recv(&file.data) {
let (payload, _) = self.recv().expect("Can't recv icmp packet");
let (payload, _) = match self.recv() {
Ok(p) => p,
Err(_) => break, // malformed recv, stop reading
};
let icmp_packet = Icmpv4Packet::new_unchecked(&payload);
//TODO: replace default with actual caps
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &Default::default()).unwrap();
// Drop packets that fail to parse — don't crash the daemon.
let Ok(icmp_repr) = Icmpv4Repr::parse(&icmp_packet, &Default::default()) else {
continue;
};
if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr {
if buf.len() < mem::size_of::<u16>() + data.len() {
return Err(SyscallError::new(syscall::EINVAL));
match file.data.socket_type {
IcmpSocketType::Echo => {
// For echo sockets, only echo replies are expected.
// Other ICMP types are silently dropped.
if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr {
if buf.len() < mem::size_of::<u16>() + data.len() {
return Err(SyscallError::new(syscall::EINVAL));
}
buf[0..2].copy_from_slice(&seq_no.to_be_bytes());
for i in 0..data.len() {
buf[mem::size_of::<u16>() + i] = data[i];
}
return Ok(mem::size_of::<u16>() + data.len());
}
}
buf[0..2].copy_from_slice(&seq_no.to_be_bytes());
for i in 0..data.len() {
buf[mem::size_of::<u16>() + i] = data[i];
IcmpSocketType::Udp => {
// For ICMP error notification endpoints (IP_RECVERR
// style), serialize the ICMP error type and original
// packet metadata. Format:
// [0]: icmp type (e.g. 3=dst unreachable, 11=time exceeded)
// [1]: icmp code
// [2..6]: reserved
// [6..]: original IP header (up to buf.len()-6)
if buf.len() < 7 {
return Err(SyscallError::new(syscall::EINVAL));
}
buf[0] = payload[0];
buf[1] = payload[1];
let copy_len = (buf.len() - 6).min(payload.len().saturating_sub(8));
if copy_len > 0 {
buf[6..6 + copy_len].copy_from_slice(&payload[8..8 + copy_len]);
}
return Ok(6 + copy_len);
}
return Ok(mem::size_of::<u16>() + data.len());
}
}
@@ -269,9 +308,9 @@ impl<'a> SchemeSocket for IcmpSocket<'a> {
fn handle_recvmsg(
&mut self,
file: &mut SchemeFile<Self>,
how: &mut [u8],
flags: usize,
_file: &mut SchemeFile<Self>,
_how: &mut [u8],
_flags: usize,
) -> SyscallResult<usize> {
return Err(SyscallError::new(syscall::EOPNOTSUPP));
}
+10 -8
View File
@@ -53,10 +53,12 @@ impl<'a> SchemeSocket for RawSocket<'a> {
}
fn hop_limit(&self) -> u8 {
0
smoltcp::socket::raw::Socket::hop_limit(self)
}
fn set_hop_limit(&mut self, _hop_limit: u8) {}
fn set_hop_limit(&mut self, hop_limit: u8) {
smoltcp::socket::raw::Socket::set_hop_limit(self, hop_limit);
}
fn new_socket(
socket_set: &mut SocketSet,
@@ -80,8 +82,8 @@ impl<'a> SchemeSocket for RawSocket<'a> {
vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE],
);
let ip_socket = RawSocket::new(
Some(IpVersion::Ipv4),
Some(IpProtocol::from(proto)),
IpVersion::Ipv4,
IpProtocol::from(proto),
rx_buffer,
tx_buffer,
);
@@ -139,16 +141,16 @@ impl<'a> SchemeSocket for RawSocket<'a> {
fn fpath(&self, _file: &SchemeFile<Self>, buf: &mut [u8]) -> SyscallResult<usize> {
FpathWriter::with(buf, "ip", |w| {
write!(w, "{}", self.ip_protocol().unwrap()).unwrap();
write!(w, "{}", self.ip_protocol()).unwrap();
Ok(())
})
}
fn handle_recvmsg(
&mut self,
file: &mut SchemeFile<Self>,
how: &mut [u8],
flags: usize,
_file: &mut SchemeFile<Self>,
_how: &mut [u8],
_flags: usize,
) -> SyscallResult<usize> {
return Err(SyscallError::new(syscall::EOPNOTSUPP));
}
+98 -16
View File
@@ -1,6 +1,7 @@
use crate::link::ethernet::EthernetLink;
use crate::link::LinkDevice;
use crate::link::{loopback::LoopbackDevice, DeviceList};
use crate::observer::{Observer, ObserverRef};
use crate::router::route_table::{RouteTable, Rule};
use crate::router::Router;
use crate::scheme::smoltcp::iface::SocketSet as SmoltcpSocketSet;
@@ -33,19 +34,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 +77,10 @@ pub struct Smolnetd {
tcp_scheme: TcpScheme,
icmp_scheme: IcmpScheme,
netcfg_scheme: NetCfgScheme,
netfilter_scheme: NetFilterScheme,
tun_scheme: TunScheme,
filter_table: FilterTableRef,
observer: ObserverRef,
}
impl Smolnetd {
@@ -79,41 +90,66 @@ impl Smolnetd {
pub const MAX_CHECK_TIMEOUT: Duration = Duration::from_millis(500);
pub fn new(
network_file: Fd,
hardware_addr: EthernetAddress,
network_files: Vec<(Fd, EthernetAddress, String)>,
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.");
// Default gateway from /etc/net/ip_router. If the file is missing
// or malformed, fall back to 0.0.0.0 and emit a warning rather
// than panicking. The route lookup below will return None for
// an invalid gateway, which is the correct degraded behavior.
let default_gw = match getcfg("ip_router") {
Ok(s) => Ipv4Address::from_str(s.trim()).unwrap_or_else(|_| {
log::warn!("smolnetd: invalid ip_router '{}' in cfg, using 0.0.0.0", s);
Ipv4Address::new(0, 0, 0, 0)
}),
Err(e) => {
log::warn!("smolnetd: ip_router not set in cfg ({:?}), using 0.0.0.0", e);
Ipv4Address::new(0, 0, 0, 0)
}
};
let devices = Rc::new(RefCell::new(DeviceList::default()));
let route_table = Rc::new(RefCell::new(RouteTable::default()));
let observer = Observer::new();
let mut network_device = Tracer::new(
Router::new(Rc::clone(&devices), Rc::clone(&route_table)),
Router::new(Rc::clone(&devices), Rc::clone(&route_table), Rc::clone(&observer)),
|_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");
// Skip the default route if the gateway is 0.0.0.0 (no gateway
// configured). smoltcp rejects a default route with 0.0.0.0 as
// gateway. Falling back to no default route is correct behavior.
if !default_gw.is_unspecified() {
if let Err(e) = iface
.routes_mut()
.add_default_ipv4_route(default_gw)
{
log::warn!("smolnetd: failed to add default route: {:?}", e);
}
}
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,14 +158,26 @@ impl Smolnetd {
Rc::clone(loopback.name()),
"127.0.0.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);
route_table.borrow_mut().insert_rule(Rule::new(
"::1/128".parse().unwrap(),
None,
Rc::clone(loopback.name()),
"::1".parse().unwrap(),
));
devices.borrow_mut().push(loopback);
devices.borrow_mut().push(eth0);
for (i, (nf, hw_addr, name)) in network_files.into_iter().enumerate() {
let dev_name = if name.is_empty() {
format!("eth{}", i)
} else {
name
};
let mut link = EthernetLink::new(&dev_name, unsafe {
File::from_raw_fd(nf.into_raw() as RawFd)
});
link.set_mac_address(hw_addr);
devices.borrow_mut().push(link);
}
Ok(Smolnetd {
iface: Rc::clone(&iface),
@@ -170,7 +218,18 @@ impl Smolnetd {
netcfg_file,
Rc::clone(&route_table),
Rc::clone(&devices),
Rc::clone(&socket_set),
ip_forward,
Rc::clone(&observer),
Rc::clone(&filter_table),
)?,
netfilter_scheme: NetFilterScheme::new(
netfilter_file,
Rc::clone(&filter_table),
)?,
tun_scheme: TunScheme::new(tun_file, Rc::clone(&devices))?,
filter_table,
observer,
})
}
@@ -215,6 +274,17 @@ impl Smolnetd {
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>() {
@@ -248,10 +318,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) {
+639 -27
View File
@@ -7,8 +7,8 @@ use redox_scheme::{
CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket,
};
use scheme_utils::HandleMap;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
use std::cell::RefCell;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address};
use std::cell::{Cell, RefCell};
use std::collections::BTreeMap;
use std::mem;
use std::rc::Rc;
@@ -22,11 +22,12 @@ use syscall::{Error as SyscallError, EventFlags as SyscallEventFlags, Result as
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};
use super::{post_fevent, Interface, SocketSet};
const WRITE_BUFFER_MAX_SIZE: usize = 0xffff;
@@ -35,6 +36,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))?;
@@ -45,24 +75,55 @@ fn parse_route(value: &str, route_table: &RouteTable) -> SyscallResult<Rule> {
.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))?,
let dev;
let via;
let src;
// Accept "via <ip>" or "dev <name>" or both.
match parts.next() {
Some("via") => {
let gw: IpAddress = parts
.next()
.ok_or(SyscallError::new(syscall::EINVAL))?
.parse()
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
if !gw.is_unicast() {
return Err(SyscallError::new(syscall::EINVAL));
}
let rule = route_table
.lookup_rule(&gw)
.ok_or(SyscallError::new(syscall::EINVAL))?;
via = Some(gw);
dev = rule.dev.clone();
src = rule.src;
}
Some("dev") => {
dev = parts
.next()
.ok_or(SyscallError::new(syscall::EINVAL))?
.into();
via = None;
// Use 0.0.0.0 as source for direct routes — the routing
// layer will pick the correct source IP when sending.
src = IpAddress::v4(0, 0, 0, 0);
}
_ => return Err(SyscallError::new(syscall::EINVAL)),
};
if !via.is_unicast() {
return Err(SyscallError::new(syscall::EINVAL));
let mut metric: u32 = 0;
if let Some(keyword) = parts.next() {
if keyword == "metric" {
metric = parts
.next()
.ok_or(SyscallError::new(syscall::EINVAL))?
.parse::<u32>()
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
} else {
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))
Ok(Rule::new(cidr, via, dev, src).with_metric(metric))
}
fn mk_root_node(
@@ -71,8 +132,202 @@ fn mk_root_node(
dns_config: DNSConfigRef,
route_table: Rc<RefCell<RouteTable>>,
devices: Rc<RefCell<DeviceList>>,
socket_set: Rc<RefCell<SocketSet>>,
ip_forward: Rc<Cell<bool>>,
observer: ObserverRef,
filter_table: Rc<RefCell<crate::filter::FilterTable>>,
) -> CfgNodeRef {
cfg_node! {
"help" => {
ro [] || {
"summary — one-read network state\n\
sockets — active socket list (TCP/UDP/ICMP)\n\
conntrack — connection tracking (stats / list)\n\
nat — NAT bindings (bindings / stats)\n\
capture — packet capture (enable/read/filter/count)\n\
sysctl — kernel tunables (net/ipv4/ip_forward)\n\
route — routing table (list/add/rm/flush/count/gateway)\n\
resolv — DNS configuration (nameserver/nameserver6)\n\
ifaces — per-interface config/stats (eth0/lo/...)\n\
version — netstack version info\n"
.to_string()
}
},
"version" => {
ro [] || {
format!("{}\n", env!("CARGO_PKG_VERSION"))
}
},
"summary" => {
ro [devices, route_table, socket_set, ip_forward, filter_table] || {
let mut out = String::new();
let devs = devices.borrow();
let eth0 = devs.get("eth0");
let lo = devs.get("loopback");
let eth_state = eth0.map(|d| d.link_state()).unwrap_or("missing");
let lo_state = lo.map(|d| d.link_state()).unwrap_or("missing");
out.push_str(&format!("interfaces: eth0={} lo={}\n", eth_state, lo_state));
out.push_str(&format!("routes: {}\n", route_table.borrow().len()));
let set = socket_set.borrow();
let mut tcp = 0u32; let mut udp = 0u32; let mut icmp = 0u32; let mut raw = 0u32;
for (_handle, socket) in set.iter() {
match socket {
smoltcp::socket::Socket::Tcp(_) => tcp += 1,
smoltcp::socket::Socket::Udp(_) => udp += 1,
smoltcp::socket::Socket::Icmp(_) => icmp += 1,
smoltcp::socket::Socket::Raw(_) => raw += 1,
_ => {}
}
}
out.push_str(&format!("sockets: {} (tcp={} udp={} icmp={} raw={})\n",
tcp + udp + icmp + raw, tcp, udp, icmp, raw));
out.push_str(&format!("ip_forward: {}\n",
if ip_forward.get() { "1" } else { "0" }));
if let Some(dev) = eth0 {
let s = dev.statistics();
out.push_str(&format!("eth0 stats: rx={}/{} tx={}/{} err={}/{} drop={}/{}\n",
s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets,
s.rx_errors, s.tx_errors, s.rx_dropped, s.tx_dropped));
}
out.push_str("filter chains:\n");
out.push_str(&filter_table.borrow().chain_summary());
if let Some(ref ct) = filter_table.borrow().conntrack {
out.push_str("conntrack stats:\n");
out.push_str(&ct.stats());
}
out
}
},
"conntrack" => {
"stats" => {
ro [filter_table] || {
let table = filter_table.borrow();
if let Some(ref ct) = table.conntrack {
ct.stats()
} else {
"conntrack: not enabled\n".to_string()
}
}
},
"list" => {
ro [filter_table] || {
let table = filter_table.borrow();
if let Some(ref ct) = table.conntrack {
ct.format()
} else {
"conntrack: not enabled\n".to_string()
}
}
},
},
"nat" => {
"bindings" => {
ro [filter_table] || {
filter_table.borrow().nat_table.format_bindings()
}
},
"stats" => {
ro [filter_table] || {
filter_table.borrow().nat_table.format()
}
},
},
"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();
let mut tcp = 0u32;
let mut udp = 0u32;
let mut icmp = 0u32;
let mut raw = 0u32;
let mut out = String::from("");
for (_handle, socket) in set.iter() {
match socket {
smoltcp::socket::Socket::Tcp(_) => tcp += 1,
smoltcp::socket::Socket::Udp(_) => udp += 1,
smoltcp::socket::Socket::Icmp(_) => icmp += 1,
smoltcp::socket::Socket::Raw(_) => raw += 1,
_ => {}
}
}
let total = tcp + udp + icmp + raw;
out.push_str(&format!("sockets: {} (tcp={} udp={} icmp={} raw={})\n",
total, tcp, udp, icmp, raw));
for (_handle, socket) in set.iter() {
if let smoltcp::socket::Socket::Tcp(tcp_sock) = socket {
let local = tcp_sock.local_endpoint();
let remote = tcp_sock.remote_endpoint();
let state = tcp_sock.state();
let sq = tcp_sock.send_queue();
let rq = tcp_sock.recv_queue();
let sc = tcp_sock.send_capacity();
let rc = tcp_sock.recv_capacity();
out.push_str(&format!("tcp: {:?} {} -> {} sendq={}/{} recvq={}/{}\n",
state,
local.map(|e| format!("{}", e)).unwrap_or_else(|| "-".to_string()),
remote.map(|e| format!("{}", e)).unwrap_or_else(|| "-".to_string()),
sq, sc, rq, rc));
}
if let smoltcp::socket::Socket::Udp(udp_sock) = socket {
let endpoint = udp_sock.endpoint();
out.push_str(&format!("udp: local={}\n",
if endpoint.port != 0 {
format!("{}", endpoint)
} else {
"-".to_string()
}));
}
}
out
}
}
},
"resolv" => {
"nameserver" => {
rw [dns_config, notifier] (Option<Ipv4Address>, None)
@@ -99,6 +354,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" => {
@@ -107,6 +391,46 @@ fn mk_root_node(
format!("{}", route_table.borrow())
}
},
"count" => {
ro [route_table] || {
let count = route_table.borrow().len();
format!("routes: {}\n", count)
}
},
"gateway" => {
ro [route_table] || {
let table = route_table.borrow();
let zero: IpAddress = IpAddress::v4(0, 0, 0, 0);
let gw = table.lookup_gateway(&zero);
match gw {
Some(addr) => format!("default via {}\n", addr),
None => "no default route\n".to_string(),
}
}
},
"lookup" => {
rw [route_table] (Option<String>, None)
|| {
"write IP address to query route\n".to_string()
}
|cur_value, line| {
let ip: IpAddress = line.trim().parse()
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
let table = route_table.borrow();
let rule = table.lookup_rule(&ip);
match rule {
Some(r) => {
*cur_value = Some(format!("{}", r));
Ok(())
}
None => {
*cur_value = Some(format!("no route to {}\n", ip));
Ok(())
}
}
}
|_cur_value| { Ok(()) }
},
"add" => {
wo [iface, notifier, route_table] (Option<Rule>, None)
|cur_value, line| {
@@ -132,13 +456,14 @@ fn mk_root_node(
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))
}
let cidr = if line.trim() == "default" {
gateway_cidr()
} else {
line.parse::<IpCidr>()
.map_err(|_| SyscallError::new(syscall::EINVAL))?
};
*cur_value = Some(cidr);
Ok(())
} else {
Err(SyscallError::new(syscall::EINVAL))
}
@@ -154,6 +479,35 @@ fn mk_root_node(
}
},
},
"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 => { *cur_value = Some(false); }
1 => { *cur_value = Some(true); }
_ => return Err(SyscallError::new(syscall::EINVAL)),
}
Ok(())
}
|cur_value| {
if let Some(enabled) = cur_value {
ip_forward.set(*enabled);
}
Ok(())
}
}
}
}
},
"ifaces" => {
"eth0" => {
"mac" => {
@@ -230,8 +584,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());
@@ -247,17 +600,266 @@ 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");
notifier.borrow_mut().schedule_notify("route/list");
}
Ok(())
}
},
"flush" => {
wo [route_table, notifier] (Option<()>, None)
|_cur_value, _line| {
Ok(())
}
|_cur_value| {
*route_table.borrow_mut() = RouteTable::default();
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(),
}
}
},
"max" => {
ro [] || {
"1024\n".to_string()
}
},
"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(())
}
},
"add" => {
wo [devices] (Option<()>, None)
|_cur_value, line| {
let s = line.trim();
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() != 2 {
return Err(SyscallError::new(syscall::EINVAL));
}
let ip: IpAddress = parts[0].parse::<IpAddress>()
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
let mut mac = [0u8; 6];
let mut idx = 0;
for part in parts[1].split(|c: char| c == ':' || c == '-') {
if idx >= 6 {
return Err(SyscallError::new(syscall::EINVAL));
}
mac[idx] = u8::from_str_radix(part, 16)
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
idx += 1;
}
if idx != 6 {
return Err(SyscallError::new(syscall::EINVAL));
}
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
dev.add_static_neighbor(ip, mac).map_err(|_| {
SyscallError::new(syscall::EINVAL)
})?;
} else {
return Err(SyscallError::new(syscall::ENODEV));
}
Ok(())
}
|_cur_value| { Ok(()) }
},
"del" => {
wo [devices] (Option<()>, None)
|_cur_value, line| {
let ip: IpAddress = line.trim().parse()
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
if !dev.remove_neighbor(ip) {
return Err(SyscallError::new(syscall::ENOENT));
}
} else {
return Err(SyscallError::new(syscall::ENODEV));
}
Ok(())
}
|_cur_value| { Ok(()) }
},
},
"stats" => {
ro [devices] || {
match devices.borrow().get("eth0") {
Some(dev) => {
let s = dev.statistics();
format!("rx_bytes={} rx_packets={} tx_bytes={} tx_packets={} rx_errors={} tx_errors={} rx_dropped={} tx_dropped={} mtu={} link={}\n",
s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets,
s.rx_errors, s.tx_errors, s.rx_dropped, s.tx_dropped,
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" => {
rw [devices] (Option<String>, None)
|| {
match devices.borrow().get("eth0") {
Some(dev) => dev.qdisc_info(),
None => "Device not found\n".into(),
}
}
|cur_value, line| {
let kind = line.trim().to_string();
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
dev.set_qdisc(&kind).map_err(|e| {
SyscallError::new(syscall::EINVAL)
})?;
*cur_value = Some(kind);
Ok(())
} else {
Err(SyscallError::new(syscall::ENODEV))
}
}
|_cur_value| { Ok(()) }
},
"enabled" => {
rw [devices] (Option<bool>, None)
|| {
match devices.borrow().get("eth0") {
Some(dev) => if dev.is_enabled() { "up\n".to_string() } else { "down\n".to_string() },
None => "Device not found\n".into(),
}
}
|cur_value, line| {
let s = line.trim();
match s {
"up" | "1" | "true" | "yes" | "on" => {
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
dev.set_enabled(true);
*cur_value = Some(true);
Ok(())
} else {
Err(SyscallError::new(syscall::ENODEV))
}
}
"down" | "0" | "false" | "no" | "off" => {
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
dev.set_enabled(false);
*cur_value = Some(false);
Ok(())
} else {
Err(SyscallError::new(syscall::ENODEV))
}
}
_ => Err(SyscallError::new(syscall::EINVAL)),
}
}
|_cur_value| { Ok(()) }
},
"promiscuous" => {
rw [devices] (Option<bool>, None)
|| {
match devices.borrow().get("eth0") {
Some(dev) => if dev.is_promiscuous() { "on\n".to_string() } else { "off\n".to_string() },
None => "Device not found\n".into(),
}
}
|cur_value, line| {
let s = line.trim();
let enabled = match s {
"on" | "1" | "true" | "yes" => true,
"off" | "0" | "false" | "no" => false,
_ => return Err(SyscallError::new(syscall::EINVAL)),
};
if let Some(dev) = devices.borrow_mut().get_mut("eth0") {
dev.set_promiscuous(enabled);
*cur_value = Some(enabled);
Ok(())
} else {
Err(SyscallError::new(syscall::ENODEV))
}
}
|_cur_value| { Ok(()) }
}
},
"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={} rx_errors={} tx_errors={} rx_dropped={} tx_dropped={} mtu={} link={}\n",
s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets,
s.rx_errors, s.tx_errors, s.rx_dropped, s.tx_dropped,
dev.mtu(), dev.link_state())
}
None => "Device not found\n".into(),
}
}
},
}
}
}
@@ -265,6 +867,7 @@ fn mk_root_node(
struct DNSConfig {
name_server: Ipv4Address,
name_server6: Option<Ipv6Address>,
}
type DNSConfigRef = Rc<RefCell<DNSConfig>>;
@@ -337,10 +940,15 @@ impl NetCfgScheme {
scheme_file: Socket,
route_table: Rc<RefCell<RouteTable>>,
devices: Rc<RefCell<DeviceList>>,
socket_set: Rc<RefCell<SocketSet>>,
ip_forward: Rc<Cell<bool>>,
observer: ObserverRef,
filter_table: Rc<RefCell<crate::filter::FilterTable>>,
) -> 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,
@@ -351,6 +959,10 @@ impl NetCfgScheme {
dns_config,
route_table,
devices,
socket_set,
ip_forward,
observer,
filter_table,
),
notifier,
};
+428
View File
@@ -0,0 +1,428 @@
//! 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, NatType, 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,
),
["conntrack", "stats"] => (
self.table.borrow().conntrack.as_ref()
.map(|ct| ct.stats().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),
["stats"] => {
let table = self.table.borrow();
let mut out = String::new();
for &hook in &Hook::ALL {
let name = hook.name();
let (pkts, bytes) = table.chain_counters.get(&hook).copied().unwrap_or((0, 0));
let policy = table.default_policy.get(&hook).copied().unwrap_or(Verdict::Accept);
out.push_str(&format!("{}: packets={} bytes={} policy={}\n", name, pkts, bytes, policy.name()));
}
let snat_count = table.nat_table.rules.iter().filter(|r| r.nat_type == NatType::Snat).count();
let dnat_count = table.nat_table.rules.len() - snat_count;
out.push_str(&format!("rules: {} active (snat={} dnat={})\n",
table.rules.len(), snat_count, dnat_count));
if let Some(ref ct) = table.conntrack {
out.push_str(&format!("conntrack: {}\n", ct.len()));
out.push_str(&ct.stats());
}
(out.into_bytes(), true, false)
},
_ => 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 path == "reset" {
*table = FilterTable::new();
log::info!("netfilter: table reset to defaults");
} else if path == "counters/reset" {
table.reset_counters();
log::info!("netfilter: counters reset (rules preserved)");
} 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))
}
}
}
+86 -37
View File
@@ -251,17 +251,34 @@ where
flags: usize,
) -> SyscallResult<usize>;
fn get_sock_opt(
&self,
file: &SchemeFile<Self>,
name: usize,
buf: &mut [u8],
fn call(
&mut self,
_file: &mut SchemeFile<Self>,
_payload: &mut [u8],
_metadata: &[u64],
_ctx: &CallerCtx,
) -> SyscallResult<usize> {
Err(SyscallError::new(syscall::EOPNOTSUPP))
}
fn get_sock_opt(
&self,
_file: &SchemeFile<Self>,
_name: usize,
_buf: &mut [u8],
) -> SyscallResult<usize> {
Err(SyscallError::new(syscall::ENOPROTOOPT))
}
fn set_sock_opt(
&mut self,
_file: &SchemeFile<Self>,
_name: usize,
_buf: &[u8],
) -> SyscallResult<usize> {
// Return Err for default implementation
Err(SyscallError::new(syscall::ENOPROTOOPT))
}
}
pub enum Handle<SocketT>
where
SocketT: SchemeSocket,
@@ -322,8 +339,8 @@ where
}?;
let mut timeout = match op {
Op::Read(_) => write_timeout,
Op::Write(_) => read_timeout,
Op::Read(_) => read_timeout,
Op::Write(_) => write_timeout,
_ => None,
};
@@ -453,10 +470,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)?;
@@ -489,7 +516,21 @@ where
Handle::SchemeRoot => Err(SyscallError::new(syscall::EBADF)),
}
}
// SocketCall::SendMsg => self.handle_sendmsg(id, payload, ctx),
SocketCall::SendMsg => {
let flags = metadata[1] as usize;
let handle = self.handles.get_mut(fd)?;
match *handle {
Handle::File(ref mut file) => {
let mut socket_set = self.socket_set.borrow_mut();
let socket = socket_set.get_mut::<SocketT>(file.socket_handle());
SocketT::call(socket, file, payload, &[flags as u64], ctx)
}
Handle::Null(_) => Err(SyscallError::new(syscall::EINVAL)),
Handle::SchemeRoot => Err(SyscallError::new(syscall::EBADF)),
}
}
// SocketCall::Unbind => self.handle_unbind(id),
// SocketCall::GetToken => self.handle_get_token(id, payload),
SocketCall::GetPeerName => {
@@ -658,32 +699,33 @@ where
let socket_handle = scheme_file.socket_handle();
let mut socket_set = self.socket_set.borrow_mut();
let socket = socket_set.get::<SocketT>(socket_handle);
let _ = socket.close_file(&scheme_file, &mut self.scheme_data);
let remove = match self.ref_counts.entry(socket_handle) {
// Compute refcount change WITHOUT calling close_file yet.
// The port release (inside close_file) must happen exactly
// once when the LAST file referencing this socket is
// closed — otherwise a dup'd socket's port would be
// double-freed.
let new_count = match self.ref_counts.entry(socket_handle) {
Entry::Vacant(_) => {
warn!("Closing a socket_handle with no ref");
true
0
}
Entry::Occupied(mut e) => {
if *e.get() == 0 {
warn!("Closing a socket_handle with no ref");
let count = *e.get();
if count <= 1 {
e.remove();
true
0
} else {
*e.get_mut() -= 1;
if *e.get() == 0 {
e.remove();
true
} else {
false
}
count - 1
}
}
};
if remove {
// Only close the socket (which releases the port) when
// the last reference is gone.
if new_count == 0 {
let socket = socket_set.get::<SocketT>(socket_handle);
let _ = socket.close_file(&scheme_file, &mut self.scheme_data);
socket_set.remove(socket_handle);
}
}
@@ -817,22 +859,27 @@ where
if let Some((socket_handle, data)) = update_with {
if let SchemeFile::Socket(ref mut file) = *file {
// We replace the socket_handle pointed by file so update the ref_counts
// accordingly
// We replace the socket_handle pointed by file so update
// the ref_counts accordingly.
self.ref_counts
.entry(file.socket_handle)
.and_modify(|e| *e = e.saturating_sub(1))
.or_insert(0);
// Increment refcount of the NEW socket (socket_handle)
// that the file is being updated to point at.
*self
.ref_counts
.entry(new_handle.socket_handle())
.entry(socket_handle)
.or_insert(0) += 1;
file.socket_handle = socket_handle;
file.data = data;
}
}
// Increment refcount for the new_handle fd being inserted.
// This is always done (regardless of update_with) because
// new_handle represents a new fd in the handle table.
*self
.ref_counts
.entry(new_handle.socket_handle())
@@ -875,12 +922,14 @@ where
}
fn fsync(&mut self, fd: usize, _ctx: &CallerCtx) -> SyscallResult<()> {
{
let _file = self.handles.get_mut(fd)?;
}
// Verify the socket exists. The netstack is event-driven (polled by
// userspace via fevent), so there is no kernel-side buffer to flush.
// POSIX fsync(2) on a socket is required to return success when the
// socket is valid; the underlying protocol (TCP) handles acknowledgements
// asynchronously through the normal poll loop.
// Cross-referenced with Linux net/socket.c: sockfs_fsync -> sock_no_fsync.
let _file = self.handles.get(fd)?;
Ok(())
// TODO Implement fsyncing
// self.0.network_fsync()
}
fn fpath(&mut self, fd: usize, buf: &mut [u8], _ctx: &CallerCtx) -> SyscallResult<usize> {
+199 -7
View File
@@ -1,10 +1,12 @@
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;
use syscall::{Error as SyscallError, Result as SyscallResult};
use anyhow::Context as _;
use super::socket::{Context, DupResult, SchemeFile, SchemeSocket, SocketFile};
use super::{parse_endpoint, SchemeWrapper, SocketSet};
@@ -13,6 +15,19 @@ use libredox::flag;
const SO_SNDBUF: usize = 7;
const SO_RCVBUF: usize = 8;
const SO_KEEPALIVE: usize = 9;
const SO_REUSEADDR: usize = 2;
const SO_LINGER: usize = 14;
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 +103,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));
}
@@ -115,20 +132,32 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
port: local_endpoint.port,
};
let allocated_port = local_endpoint.port;
let allocated_ephemeral = local_endpoint_addr.is_none();
trace!("Connecting tcp {} {}", local_endpoint, remote_endpoint);
tcp_socket
if let Err(e) = tcp_socket
.connect(
context.iface.borrow_mut().context(),
IpEndpoint::new(remote_endpoint.addr.unwrap(), remote_endpoint.port),
local_endpoint,
)
.expect("Can't connect tcp socket ");
.map_err(|_| SyscallError::new(syscall::EIO))
{
// Connect failed. Release the auto-allocated port (if
// any) so the next attempt can use it. Explicit user-
// provided ports are released by on_close when the
// last file is dropped.
if allocated_ephemeral {
port_set.release_port(allocated_port);
}
return Err(e);
}
None
} else {
trace!("Listening tcp {}", local_endpoint);
tcp_socket
.listen(local_endpoint)
.expect("Can't listen on local endpoint");
.map_err(|_| SyscallError::new(syscall::EIO))?;
Some(local_endpoint)
};
@@ -162,7 +191,8 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
} else if !self.is_active() {
Err(SyscallError::new(syscall::ENOTCONN))
} else if self.can_send() {
Ok(self.send_slice(buf).expect("Can't send slice"))
self.send_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?;
Ok(buf.len())
} else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK {
Err(SyscallError::new(syscall::EAGAIN))
} else {
@@ -180,7 +210,7 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
} else if !self.is_active() {
Err(SyscallError::new(syscall::ENOTCONN))
} else if self.can_recv(&file.data) {
let length = self.recv_slice(buf).expect("Can't receive slice");
let length = self.recv_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?;
Ok(length)
} else if !self.may_recv() {
Ok(0)
@@ -236,7 +266,7 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
let tcp_socket = socket_set.get_mut::<TcpSocket>(new_socket_handle);
tcp_socket
.listen(listen_enpoint)
.expect("Can't listen on local endpoint");
.map_err(|_| SyscallError::new(syscall::EIO))?;
}
// We got a new connection to the socket so acquire the port
port_set.acquire_port(
@@ -402,6 +432,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();
@@ -424,7 +461,162 @@ 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_rcv_queuelen: self.recv_queue() as u32,
tcpi_rto: 3000,
tcpi_snd_cwnd: self.send_capacity() as u32,
tcpi_rcv_wnd: self.recv_capacity() as u32,
tcpi_snd_mss: 1460,
};
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(2) collides with TCP_MAXSEG(2) — TCP_MAXSEG handles it
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)
}
SO_LINGER => {
// struct linger: l_onoff (4 bytes) + l_linger (4 bytes)
let vals = [1i32, 0i32]; // on, 0s linger
let bytes = unsafe {
core::slice::from_raw_parts(vals.as_ptr() as *const u8, 8)
};
let len = buf.len().min(bytes.len());
buf[..len].copy_from_slice(&bytes[..len]);
Ok(len)
}
// SO_REUSEADDR(2) collides with TCP_MAXSEG(2) — TCP_MAXSEG handles it
_ => 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),
SO_LINGER => Ok(1), // accepted, smoltcp handles close semantics
// SO_REUSEADDR(2) collides with IP_TTL(2) — IP_TTL handles it
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}
}
#[repr(C)]
struct TcpInfo {
tcpi_state: u8,
_pad: [u8; 3],
tcpi_snd_queuelen: u32,
tcpi_rcv_queuelen: u32,
tcpi_rto: u32,
tcpi_snd_cwnd: u32,
tcpi_rcv_wnd: u32,
tcpi_snd_mss: u32,
}
+269
View File
@@ -0,0 +1,269 @@
//! 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 {
// Drop accumulated network->userspace packets when userspace
// is not reading. The actual transfer from dev.rx to the
// network happens via TunDevice::recv() called by the polling
// thread.
let mut tx = dev.tx.borrow_mut();
let stale = tx.len();
tx.clear();
if stale > 0 {
log::debug!("tun: dropped {} stale device->user packets", stale);
}
}
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(())
}
}
+109 -14
View File
@@ -1,9 +1,10 @@
use scheme_utils::FpathWriter;
use redox_scheme::CallerCtx;
use smoltcp::iface::SocketHandle;
use smoltcp::socket::udp::{
PacketBuffer as UdpSocketBuffer, PacketMetadata as UdpPacketMetadata, Socket as UdpSocket,
};
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint};
use std::str;
use syscall;
use syscall::{Error as SyscallError, Result as SyscallResult};
@@ -16,6 +17,9 @@ use libredox::flag;
const SO_SNDBUF: usize = 7;
const SO_RCVBUF: usize = 8;
const SO_REUSEADDR: usize = 2;
const SO_BROADCAST: usize = 6;
const IP_TTL: usize = 2;
pub type UdpScheme = SchemeWrapper<UdpSocket<'static>>;
@@ -48,7 +52,13 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
match self.peek() {
Ok((_, meta)) => {
let source = meta.endpoint;
let connected_addr = data.addr.unwrap(); // Safe because is_specified() checked it
// is_specified() is true when port is non-zero even if
// addr is None (e.g. "udp/:53"). In that case skip the
// match: accept all packets (the spec_only check below
// already handles the unspecified-addr case).
let Some(connected_addr) = data.addr else {
return true;
};
// Allow Broadcast special case (DHCP)
let is_broadcast = match connected_addr {
@@ -132,7 +142,9 @@ impl<'a> SchemeSocket for UdpSocket<'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));
}
@@ -142,14 +154,19 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
if remote_endpoint.is_specified() {
let local_endpoint_addr = match local_endpoint.addr {
Some(addr) if addr.is_unspecified() => Some(addr),
Some(addr) if !addr.is_unspecified() => {
// Local IP is explicitly set — use it.
Some(addr)
}
_ => {
// local ip is 0.0.0.0, resolve it
// Local IP is 0.0.0.0 or unspecified. Look up the
// source address from the route table based on the
// remote destination.
let route_table = context.route_table.borrow();
let addr = route_table
.lookup_src_addr(&remote_endpoint.addr.expect("Checked in is_specified"));
if matches!(addr, None) {
error!("Opening a TCP connection with a probably invalid source IP as no route have been found for destination: {}", remote_endpoint);
error!("Opening a UDP connection with a probably invalid source IP as no route have been found for destination: {}", remote_endpoint);
}
addr
}
@@ -162,7 +179,7 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
udp_socket
.bind(local_endpoint)
.expect("Can't bind udp socket to local endpoint");
.map_err(|_| SyscallError::new(syscall::EIO))?;
Ok((socket_handle, remote_endpoint))
}
@@ -183,12 +200,13 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
file: &mut SocketFile<Self::DataT>,
buf: &[u8],
) -> SyscallResult<usize> {
if !file.data.is_specified() {
return Err(SyscallError::new(syscall::EADDRNOTAVAIL));
}
if !file.write_enabled {
return Err(SyscallError::new(syscall::EPIPE));
}
// Unconnected sockets can only send via sendmsg/sendto.
if !file.data.is_specified() {
return Err(SyscallError::new(syscall::EDESTADDRREQ));
}
if self.can_send() {
let endpoint = file.data;
let endpoint = IpEndpoint::new(
@@ -197,12 +215,64 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
.expect("If we can send, this should be specified"),
endpoint.port,
);
self.send_slice(buf, endpoint).expect("Can't send slice");
self.send_slice(buf, endpoint).map_err(|_| SyscallError::new(syscall::EIO))?;
Ok(buf.len())
} else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK {
Err(SyscallError::new(syscall::EAGAIN))
} else {
Err(SyscallError::new(syscall::EWOULDBLOCK)) // internally scheduled to re-read
Err(SyscallError::new(syscall::EWOULDBLOCK))
}
}
fn call(
&mut self,
file: &mut SchemeFile<Self>,
how: &mut [u8],
metadata: &[u64],
_ctx: &redox_scheme::CallerCtx,
) -> SyscallResult<usize> {
let flags = metadata.first().copied().unwrap_or(0) as usize;
let socket_file = match file {
SchemeFile::Socket(ref sock_f) => sock_f,
_ => return Err(SyscallError::new(syscall::EBADF)),
};
if !socket_file.write_enabled {
return Err(SyscallError::new(syscall::EPIPE));
}
if self.can_send() {
let usize_length = core::mem::size_of::<usize>();
if how.len() < 3 * usize_length {
return Err(SyscallError::new(syscall::EINVAL));
}
let name_len = usize::from_le_bytes(
how[0..usize_length].try_into().map_err(|_| SyscallError::new(syscall::EINVAL))?,
);
let payload_len = usize::from_le_bytes(
how[usize_length..2 * usize_length]
.try_into()
.map_err(|_| SyscallError::new(syscall::EINVAL))?,
);
// msg_controllen at [2*usize_length..3*usize_length], not used.
let addr_start = 3 * usize_length;
let payload_start = addr_start + name_len;
if payload_start + payload_len > how.len() {
return Err(SyscallError::new(syscall::EINVAL));
}
// Parse destination from the sendmsg address.
let addr_str = core::str::from_utf8(&how[addr_start..addr_start + name_len])
.map_err(|_| SyscallError::new(syscall::EINVAL))?;
let dest = parse_endpoint(addr_str);
let endpoint = IpEndpoint::new(
dest.addr.unwrap_or(IpAddress::v4(0, 0, 0, 0)),
dest.port,
);
self.send_slice(&how[payload_start..payload_start + payload_len], endpoint)
.map_err(|_| SyscallError::new(syscall::EIO))?;
Ok(payload_len)
} else if socket_file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK {
Err(SyscallError::new(syscall::EAGAIN))
} else {
Err(SyscallError::new(syscall::EWOULDBLOCK))
}
}
@@ -214,7 +284,7 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
if !file.read_enabled {
Ok(0)
} else if self.can_recv(&file.data) {
let (length, _) = self.recv_slice(buf).expect("Can't receive slice");
let (length, _) = self.recv_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?;
Ok(length)
} else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK {
Err(SyscallError::new(syscall::EAGAIN))
@@ -342,7 +412,7 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
let mut payload_tmp = vec![0u8; prepared_whole_iov_size];
let (length, address) = self
.recv_slice(&mut payload_tmp)
.expect("Can't recieve slice");
.map_err(|_| SyscallError::new(syscall::EIO))?;
//Address Handling
let address_formatted = if prepared_name_len > 0 {
@@ -431,6 +501,31 @@ impl<'a> SchemeSocket for UdpSocket<'a> {
buf[..bytes.len()].copy_from_slice(&bytes);
Ok(bytes.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)
}
// SO_REUSEADDR(2) collides with IP_TTL(2) — IP_TTL handles it
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}
fn set_sock_opt(
&mut self,
_file: &SchemeFile<Self>,
name: usize,
buf: &[u8],
) -> SyscallResult<usize> {
match name {
IP_TTL => {
let val = buf.first().copied().unwrap_or(64);
self.set_hop_limit(Some(val));
Ok(1)
}
// SO_REUSEADDR(2) and SO_BROADCAST(6): accepted, no state change needed
_ => Err(SyscallError::new(syscall::ENOPROTOOPT)),
}
}