efa24228e3
CRITICAL BUGS FIXED:
1. STP build_bpdu runtime panic (link/stp.rs:121-123)
- Duration::total_millis() returns i64, to_be_bytes() = [u8; 8]
- But destination buffers (buf[29..31], buf[31..33], buf[33..35]) are 2 bytes
- copy_from_slice panics on length mismatch
- Triggered by every root bridge hello BPDU — daemon crash
- Fix: convert to ticks (1s = 256 ticks per IEEE 802.1D) as u16
2. SLAAC PIO off-by-2 byte parsing (slaac.rs:155-180)
- parse_router_advertisement used opt_data[2] for prefix length
- After pos += 2 consumed type+length, opt_data[0] is the prefix length
- All PIO fields were off by 2 — SLAAC got wrong prefix length, flags,
valid_lifetime (read Preferred Lifetime), preferred_lifetime (read Reserved2)
- Fix: use opt_data[0] for prefix length, [1] for flags, [2..6] for valid,
[6..10] for preferred, [14..30] for prefix bytes
3. ICMPv4 error wrong IHL extraction (icmp_error.rs:25, 62)
- (ipv4.version() & 0x0f) * 4 always computed 16
- smoltcp's version() returns 4 (version), not combined byte
- Fix: use ipv4.header_len()
4. UDP can_recv panic on port-only endpoint (scheme/udp.rs:54)
- data.addr.unwrap() panics if addr is None (e.g. udp/:53)
- is_specified() returns true when port is non-zero, even if addr is None
- Fix: use let-else pattern, accept all packets if addr is None
5. TCP .expect() calls crash daemon (scheme/tcp.rs)
- 5 .expect() calls in connect/listen/send/recv paths
- Any socket error panics the entire netstack daemon
- Fix: replace with .map_err() returning EIO
6. ICMP .unwrap() on malformed packets (scheme/icmp.rs:217-220)
- recv().expect() panics, Icmpv4Repr::parse().unwrap() panics
- Crafted/malformed ICMP packets would crash the daemon
- Fix: use match, drop unparseable packets
NEW TESTS (6 added, 29 total now passing):
- icmp_error::icmpv4_short_packet_returns_none
- icmp_error::icmpv4_preserves_destination_address (regression for bug 3)
- icmp_error::icmpv4_with_ip_options_includes_extended_header
- slaac::ra_with_pio_64_parses_correctly (regression for bug 2)
- link::stp::bpdu_minimal_parses
- link::stp::bpdu_short_returns_none
- link::stp::bpdu_wrong_protocol_returns_none
- link::stp::build_bpdu_does_not_panic (regression for bug 1)
The SLAAC test would have failed before the off-by-2 fix.
The STP build_bpdu test would have panicked before the ticks fix.
The ICMP tests verify the full IP header (incl. IHL=6 options) is preserved.
311 lines
10 KiB
Rust
311 lines
10 KiB
Rust
use scheme_utils::FpathWriter;
|
|
use smoltcp::iface::SocketHandle;
|
|
use smoltcp::socket::icmp::{
|
|
Endpoint as IcmpEndpoint, PacketBuffer as IcmpSocketBuffer,
|
|
PacketMetadata as IcmpPacketMetadata, Socket as IcmpSocket,
|
|
};
|
|
use smoltcp::wire::{Icmpv4Packet, Icmpv4Repr, IpAddress, IpListenEndpoint};
|
|
use std::mem;
|
|
use std::str;
|
|
use syscall;
|
|
use syscall::{Error as SyscallError, Result as SyscallResult};
|
|
|
|
use super::socket::{Context, DupResult, SchemeFile, SchemeSocket, SocketFile};
|
|
use super::{SchemeWrapper, Smolnetd, SocketSet};
|
|
use crate::port_set::PortSet;
|
|
use crate::router::Router;
|
|
|
|
pub type IcmpScheme = SchemeWrapper<IcmpSocket<'static>>;
|
|
|
|
enum IcmpSocketType {
|
|
Echo,
|
|
Udp,
|
|
}
|
|
|
|
pub struct IcmpData {
|
|
socket_type: IcmpSocketType,
|
|
ip: IpAddress,
|
|
ident: u16,
|
|
}
|
|
|
|
impl<'a> SchemeSocket for IcmpSocket<'a> {
|
|
type SchemeDataT = PortSet;
|
|
type DataT = IcmpData;
|
|
type SettingT = ();
|
|
|
|
fn new_scheme_data() -> Self::SchemeDataT {
|
|
PortSet::new(1u16, 0xffffu16).expect("Wrong ICMP ident values")
|
|
}
|
|
|
|
fn can_send(&self) -> bool {
|
|
self.can_send()
|
|
}
|
|
|
|
fn can_recv(&mut self, _data: &Self::DataT) -> bool {
|
|
smoltcp::socket::icmp::Socket::can_recv(self)
|
|
}
|
|
|
|
fn may_recv(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn get_setting(
|
|
_file: &SocketFile<Self::DataT>,
|
|
_setting: Self::SettingT,
|
|
_buf: &mut [u8],
|
|
) -> SyscallResult<usize> {
|
|
Ok(0)
|
|
}
|
|
|
|
fn set_setting(
|
|
_file: &mut SocketFile<Self::DataT>,
|
|
_setting: Self::SettingT,
|
|
_buf: &[u8],
|
|
) -> SyscallResult<usize> {
|
|
Ok(0)
|
|
}
|
|
|
|
fn hop_limit(&self) -> u8 {
|
|
self.hop_limit().unwrap_or(64)
|
|
}
|
|
|
|
fn set_hop_limit(&mut self, hop_limit: u8) {
|
|
self.set_hop_limit(Some(hop_limit));
|
|
}
|
|
|
|
fn new_socket(
|
|
socket_set: &mut SocketSet,
|
|
path: &str,
|
|
_uid: u32,
|
|
ident_set: &mut Self::SchemeDataT,
|
|
_context: &Context,
|
|
) -> SyscallResult<(SocketHandle, Self::DataT)> {
|
|
use std::str::FromStr;
|
|
|
|
let mut parts = path.split('/');
|
|
let method = parts
|
|
.next()
|
|
.ok_or_else(|| syscall::Error::new(syscall::EINVAL))?;
|
|
|
|
match method {
|
|
"echo" => {
|
|
let addr = parts
|
|
.next()
|
|
.ok_or_else(|| syscall::Error::new(syscall::EINVAL))?;
|
|
let ip =
|
|
IpAddress::from_str(addr).map_err(|_| syscall::Error::new(syscall::EINVAL))?;
|
|
|
|
let socket = IcmpSocket::new(
|
|
IcmpSocketBuffer::new(
|
|
vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE],
|
|
vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE],
|
|
),
|
|
IcmpSocketBuffer::new(
|
|
vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE],
|
|
vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE],
|
|
),
|
|
);
|
|
let handle = socket_set.add(socket);
|
|
let icmp_socket = socket_set.get_mut::<IcmpSocket>(handle);
|
|
let ident = ident_set
|
|
.get_port()
|
|
.ok_or_else(|| SyscallError::new(syscall::EINVAL))?;
|
|
icmp_socket
|
|
.bind(IcmpEndpoint::Ident(ident))
|
|
.map_err(|_| syscall::Error::new(syscall::EINVAL))?;
|
|
let socket_data = IcmpData {
|
|
socket_type: IcmpSocketType::Echo,
|
|
ident,
|
|
ip,
|
|
};
|
|
Ok((handle, socket_data))
|
|
}
|
|
"udp" => {
|
|
let addr = parts
|
|
.next()
|
|
.ok_or_else(|| syscall::Error::new(syscall::EINVAL))?;
|
|
let ip =
|
|
IpAddress::from_str(addr).map_err(|_| syscall::Error::new(syscall::EINVAL))?;
|
|
|
|
let socket = IcmpSocket::new(
|
|
IcmpSocketBuffer::new(
|
|
vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE],
|
|
vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE],
|
|
),
|
|
IcmpSocketBuffer::new(
|
|
vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE],
|
|
vec![0; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE],
|
|
),
|
|
);
|
|
let handle = socket_set.add(socket);
|
|
let icmp_socket = socket_set.get_mut::<IcmpSocket>(handle);
|
|
let ident = ident_set
|
|
.get_port()
|
|
.ok_or_else(|| SyscallError::new(syscall::EINVAL))?;
|
|
icmp_socket
|
|
.bind(IcmpEndpoint::Udp(IpListenEndpoint::from(ident)))
|
|
.map_err(|_| syscall::Error::new(syscall::EINVAL))?;
|
|
let socket_data = IcmpData {
|
|
socket_type: IcmpSocketType::Udp,
|
|
ident,
|
|
ip,
|
|
};
|
|
Ok((handle, socket_data))
|
|
}
|
|
_ => Err(syscall::Error::new(syscall::EINVAL)),
|
|
}
|
|
}
|
|
|
|
fn close_file(
|
|
&self,
|
|
file: &SchemeFile<Self>,
|
|
ident_set: &mut Self::SchemeDataT,
|
|
) -> SyscallResult<()> {
|
|
if let SchemeFile::Socket(ref file) = *file {
|
|
ident_set.release_port(file.data.ident);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_buf(
|
|
&mut self,
|
|
file: &mut SocketFile<Self::DataT>,
|
|
buf: &[u8],
|
|
) -> SyscallResult<usize> {
|
|
if !file.write_enabled {
|
|
return Err(SyscallError::new(syscall::EPIPE));
|
|
} else if self.can_send() {
|
|
match file.data.socket_type {
|
|
IcmpSocketType::Echo => {
|
|
if buf.len() < mem::size_of::<u16>() {
|
|
return Err(SyscallError::new(syscall::EINVAL));
|
|
}
|
|
let (&seq_buf, payload) = buf.split_first_chunk::<2>().unwrap();
|
|
let seq_no = u16::from_be_bytes(seq_buf);
|
|
let icmp_repr = Icmpv4Repr::EchoRequest {
|
|
ident: file.data.ident,
|
|
seq_no,
|
|
data: payload,
|
|
};
|
|
|
|
let icmp_payload = self
|
|
.send(icmp_repr.buffer_len(), file.data.ip)
|
|
.map_err(|_| syscall::Error::new(syscall::EINVAL))?;
|
|
let mut icmp_packet = Icmpv4Packet::new_unchecked(icmp_payload);
|
|
//TODO: replace Default with actual caps
|
|
icmp_repr.emit(&mut icmp_packet, &Default::default());
|
|
Ok(buf.len())
|
|
}
|
|
IcmpSocketType::Udp => Err(SyscallError::new(syscall::EINVAL)),
|
|
}
|
|
} 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
|
|
}
|
|
}
|
|
|
|
fn read_buf(
|
|
&mut self,
|
|
file: &mut SocketFile<Self::DataT>,
|
|
buf: &mut [u8],
|
|
) -> SyscallResult<usize> {
|
|
if !file.read_enabled {
|
|
return Ok(0);
|
|
}
|
|
while self.can_recv(&file.data) {
|
|
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
|
|
// 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));
|
|
}
|
|
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());
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
fn dup(
|
|
_socket_set: &mut SocketSet,
|
|
_file: &mut SchemeFile<Self>,
|
|
_path: &str,
|
|
_: &mut Self::SchemeDataT,
|
|
) -> SyscallResult<DupResult<Self>> {
|
|
Err(SyscallError::new(syscall::EBADF))
|
|
}
|
|
|
|
fn fpath(&self, file: &SchemeFile<Self>, buf: &mut [u8]) -> SyscallResult<usize> {
|
|
FpathWriter::with(buf, "icmp", |w| {
|
|
if let SchemeFile::Socket(ref socket_file) = *file {
|
|
match socket_file.data.socket_type {
|
|
IcmpSocketType::Echo => {
|
|
write!(w, "echo/{}", socket_file.data.ip).unwrap();
|
|
}
|
|
IcmpSocketType::Udp => {
|
|
write!(w, "udp/{}", socket_file.data.ip).unwrap();
|
|
}
|
|
}
|
|
Ok(())
|
|
} else {
|
|
Err(SyscallError::new(syscall::EBADF))
|
|
}
|
|
})
|
|
}
|
|
|
|
fn handle_recvmsg(
|
|
&mut self,
|
|
_file: &mut SchemeFile<Self>,
|
|
_how: &mut [u8],
|
|
_flags: usize,
|
|
) -> SyscallResult<usize> {
|
|
return Err(SyscallError::new(syscall::EOPNOTSUPP));
|
|
}
|
|
|
|
fn handle_get_peer_name(
|
|
&self,
|
|
file: &SchemeFile<Self>,
|
|
buf: &mut [u8],
|
|
) -> SyscallResult<usize> {
|
|
self.fpath(file, buf)
|
|
}
|
|
|
|
fn handle_shutdown(&mut self, file: &mut SchemeFile<Self>, how: usize) -> SyscallResult<usize> {
|
|
let socket_file = match file {
|
|
SchemeFile::Socket(ref mut file) => file,
|
|
_ => return Err(SyscallError::new(syscall::EBADF)),
|
|
};
|
|
|
|
match how {
|
|
0 => socket_file.read_enabled = false, // SHUT_RD
|
|
1 => socket_file.write_enabled = false, // SHUT_WR
|
|
2 => {
|
|
socket_file.read_enabled = false;
|
|
socket_file.write_enabled = false;
|
|
} // SHUT_RDWR
|
|
_ => return Err(SyscallError::new(syscall::EINVAL)),
|
|
}
|
|
Ok(0)
|
|
}
|
|
}
|