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:
+53
-14
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user