Merge pull request #1 from batonius/icmpd

Initial ICMP support
This commit is contained in:
Jeremy Soller
2017-06-13 13:19:07 -06:00
committed by GitHub
5 changed files with 696 additions and 1 deletions
+5 -1
View File
@@ -18,8 +18,12 @@ path = "src/tcpd/main.rs"
name = "udpd"
path = "src/udpd/main.rs"
[[bin]]
name = "icmpd"
path = "src/icmpd/main.rs"
[dependencies]
netutils = { git = "https://github.com/redox-os/netutils.git" }
rand = "0.3"
redox_event = "0.1"
redox_event = { git = "https://github.com/redox-os/event.git" }
redox_syscall = "0.1"
+92
View File
@@ -0,0 +1,92 @@
use std::convert;
use std::fmt;
use std::io::Error as IOError;
use std::result;
use syscall::error::Error as SyscallError;
pub enum PacketError {
NotEnoughData,
IncorrectChecksum,
NoEchoHeader,
SubheaderAlreadPresent,
}
enum ErrorType {
Syscall(SyscallError),
IOError(IOError),
PacketError(PacketError),
}
pub struct Error {
error_type: ErrorType,
descr: String,
}
impl Error {
pub fn from_parsing_error<S: Into<String>>(parsing_error: PacketError, descr: S) -> Error {
Error {
error_type: ErrorType::PacketError(parsing_error),
descr: descr.into(),
}
}
pub fn from_syscall_error<S: Into<String>>(syscall_error: SyscallError, descr: S) -> Error {
Error {
error_type: ErrorType::Syscall(syscall_error),
descr: descr.into(),
}
}
pub fn from_io_error<S: Into<String>>(io_error: IOError, descr: S) -> Error {
Error {
error_type: ErrorType::IOError(io_error),
descr: descr.into(),
}
}
pub fn is_unrecoverable(&self) -> bool {
match self.error_type {
ErrorType::PacketError(_) => false,
ErrorType::IOError(_) |
ErrorType::Syscall(_) => true,
}
}
}
impl fmt::Display for PacketError {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
write!(f, "{}", match *self {
PacketError::NotEnoughData => "not enough data",
PacketError::IncorrectChecksum => "checksum error",
PacketError::NoEchoHeader => "echo header is missing",
PacketError::SubheaderAlreadPresent => "subheader is already present",
})
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
match self.error_type {
ErrorType::Syscall(ref syscall_error) => {
write!(f, "{} : syscall error: {}", self.descr, syscall_error)
}
ErrorType::IOError(ref io_error) => {
write!(f, "{} : io error : {}", self.descr, io_error)
}
ErrorType::PacketError(ref parsign_error) => {
write!(f,
"{} : packet parsing error : {}",
self.descr,
parsign_error)
}
}
}
}
impl convert::From<IOError> for Error {
fn from(e: IOError) -> Self {
Error::from_io_error(e, "")
}
}
pub type Result<T> = result::Result<T, Error>;
pub type PacketResult<T> = result::Result<T, PacketError>;
+78
View File
@@ -0,0 +1,78 @@
extern crate event;
extern crate syscall;
extern crate netutils;
use error::{Result, Error};
use event::EventQueue;
use scheme::Icmpd;
use std::cell::RefCell;
use std::fs::File;
use std::os::unix::io::{RawFd, FromRawFd};
use std::process;
use std::rc::Rc;
mod error;
mod packet;
mod scheme;
fn run() -> Result<()> {
use syscall::flag::*;
if unsafe { syscall::clone(0).unwrap() } != 0 {
return Ok(());
}
let icmp_fd = syscall::open("ip:1", O_RDWR | O_NONBLOCK)
.map_err(|e| Error::from_syscall_error(e, "failed to open ip:1"))? as
RawFd;
let scheme_fd = syscall::open(":icmp", O_RDWR | O_CREAT | O_NONBLOCK)
.map_err(|e| Error::from_syscall_error(e, "failed to open :icmp"))? as
RawFd;
let icmpd = Rc::new(RefCell::new(Icmpd::new(unsafe { File::from_raw_fd(icmp_fd) },
unsafe { File::from_raw_fd(scheme_fd) })));
let mut event_queue =
EventQueue::<(), Error>::new()
.map_err(|e| Error::from_io_error(e, "failed to create event queue"))?;
let icmpd_ = icmpd.clone();
event_queue
.add(icmp_fd, move |_fd| {
if let Err(err) = icmpd_.borrow_mut().on_icmp_packet() {
if err.is_unrecoverable() {
return Err(err);
} else {
println!("icmpd: network error: {}", err);
}
}
Ok(None)
})
.map_err(|e| Error::from_io_error(e, "failed to listen to events on ip:1"))?;
event_queue
.add(scheme_fd, move |_fd| {
if let Err(err) = icmpd.borrow_mut().on_scheme_event() {
if err.is_unrecoverable() {
return Err(err);
} else {
println!("icmpd: scheme error: {}", err);
}
}
Ok(None)
})
.map_err(|e| Error::from_io_error(e, "failed to listen to events on icmp"))?;
event_queue.run()
}
fn main() {
if let Err(err) = run() {
println!("icmpd: {}", err);
process::exit(1);
}
process::exit(0);
}
+212
View File
@@ -0,0 +1,212 @@
use error::{PacketResult, PacketError};
use netutils::Checksum;
use std::mem;
const ECHO_REQUEST_TYPE: u8 = 8;
const ECHO_REQUEST_CODE: u8 = 0;
const ECHO_RESPONSE_TYPE: u8 = 0;
const ECHO_RESPONSE_CODE: u8 = 0;
const UNREACHABLE_TYPE: u8 = 3;
const UNREACHABLE_HOST_CODE: u8 = 1;
const UNREACHABLE_PROTO_CODE: u8 = 2;
const UNREACHABLE_PORT_CODE: u8 = 3;
#[repr(packed)]
pub struct Header {
icmp_type: u8,
icmp_code: u8,
crc: u16,
}
#[derive(Copy, Clone)]
#[repr(packed)]
pub struct EchoHeader {
id: u16,
//Seq is set by the caller
}
pub enum SubHeader<'a> {
Echo(&'a EchoHeader),
None,
}
pub struct Packet<'a> {
header: &'a Header,
payload: &'a [u8],
subheader: SubHeader<'a>,
}
pub struct MutPacket<'a> {
header: &'a mut Header,
payload: &'a mut [u8],
subheader: SubHeader<'a>,
}
pub enum PacketKind {
EchoRequest,
EchoResponse,
HostUnreachable,
PortUnreachable,
ProtoUnreachable,
Unknown,
}
impl EchoHeader {
pub fn new(id: u16) -> EchoHeader {
EchoHeader { id: id.to_be() }
}
pub fn get_id(&self) -> u16 {
u16::from_be(self.id)
}
}
impl<'a> SubHeader<'a> {
pub fn get_size(&self) -> usize {
match *self {
SubHeader::None => 0,
SubHeader::Echo(_) => mem::size_of::<EchoHeader>(),
}
}
}
impl<'a> Packet<'a> {
pub fn from_bytes<'b>(bytes: &'b [u8]) -> PacketResult<Packet<'a>>
where 'b: 'a
{
if bytes.len() < mem::size_of::<Header>() {
Err(PacketError::NotEnoughData)
} else {
let (header_bytes, payload_bytes) = bytes.split_at(mem::size_of::<Header>());
let mut packet = Packet {
header: unsafe { &*(header_bytes.as_ptr() as *const Header) },
payload: payload_bytes,
subheader: SubHeader::None,
};
if !packet.is_checksum_ok() {
return Err(PacketError::IncorrectChecksum);
}
match packet.get_kind() {
PacketKind::EchoResponse => {
if packet.payload.len() < mem::size_of::<EchoHeader>() {
return Err(PacketError::NoEchoHeader);
}
let (echo_header_payload, payload) =
packet.payload.split_at(mem::size_of::<EchoHeader>());
packet.subheader = SubHeader::Echo(unsafe {
&*(echo_header_payload.as_ptr() as
*const EchoHeader)
});
packet.payload = payload;
Ok(packet)
}
_ => Ok(packet),
}
}
}
fn is_checksum_ok(&self) -> bool {
let header_ptr = self.header as *const Header as usize;
let total_size = self.get_total_data_size();
let mut crc = unsafe { Checksum::sum(header_ptr, total_size) };
crc -= u16::from_be(self.header.crc) as usize;
let crc = Checksum::compile(crc);
crc == u16::from_be(self.header.crc)
}
pub fn get_kind(&self) -> PacketKind {
match (self.header.icmp_type, self.header.icmp_code) {
(ECHO_REQUEST_TYPE, ECHO_REQUEST_CODE) => PacketKind::EchoRequest,
(ECHO_RESPONSE_TYPE, ECHO_RESPONSE_CODE) => PacketKind::EchoResponse,
(UNREACHABLE_TYPE, UNREACHABLE_HOST_CODE) => PacketKind::HostUnreachable,
(UNREACHABLE_TYPE, UNREACHABLE_PROTO_CODE) => PacketKind::ProtoUnreachable,
(UNREACHABLE_TYPE, UNREACHABLE_PORT_CODE) => PacketKind::PortUnreachable,
_ => PacketKind::Unknown,
}
}
pub fn get_payload(&self) -> &[u8] {
self.payload
}
pub fn get_total_data_size(&self) -> usize {
mem::size_of::<Header>() + self.subheader.get_size() + self.payload.len()
}
pub fn get_subheader(&self) -> &SubHeader<'a> {
&self.subheader
}
}
impl<'a> MutPacket<'a> {
pub fn from_bytes<'b>(bytes: &'b mut [u8]) -> PacketResult<MutPacket<'a>>
where 'b: 'a
{
if bytes.len() < mem::size_of::<Header>() {
Err(PacketError::NotEnoughData)
} else {
let (header_bytes, payload_bytes) = bytes.split_at_mut(mem::size_of::<Header>());
Ok(MutPacket {
header: unsafe { &mut *(header_bytes.as_ptr() as *mut Header) },
payload: payload_bytes,
subheader: SubHeader::None,
})
}
}
pub fn set_subheader(self, subheader: &SubHeader) -> PacketResult<MutPacket<'a>> {
match self.subheader {
SubHeader::None => {}
_ => return Err(PacketError::SubheaderAlreadPresent),
};
if self.payload.len() < subheader.get_size() {
return Err(PacketError::NotEnoughData);
}
let (subheader_bytes, new_payload) = self.payload.split_at_mut(subheader.get_size());
let new_subheader = match *subheader {
SubHeader::Echo(echo_sub_header) => {
let echo_sub_header_mut: &mut EchoHeader =
unsafe { &mut *(subheader_bytes.as_ptr() as *mut EchoHeader) };
*echo_sub_header_mut = *echo_sub_header;
SubHeader::Echo(echo_sub_header_mut)
}
SubHeader::None => SubHeader::None,
};
Ok(MutPacket {
header: self.header,
payload: new_payload,
subheader: new_subheader,
})
}
pub fn set_kind(&mut self, packet_type: PacketKind) {
let (new_type, new_code) = match packet_type {
PacketKind::EchoRequest => (ECHO_REQUEST_TYPE, ECHO_REQUEST_CODE),
PacketKind::EchoResponse => (ECHO_RESPONSE_TYPE, ECHO_RESPONSE_CODE),
PacketKind::HostUnreachable => (UNREACHABLE_TYPE, UNREACHABLE_HOST_CODE),
PacketKind::PortUnreachable => (UNREACHABLE_TYPE, UNREACHABLE_PORT_CODE),
PacketKind::ProtoUnreachable => (UNREACHABLE_TYPE, UNREACHABLE_PROTO_CODE),
PacketKind::Unknown => (self.header.icmp_type, self.header.icmp_code),
};
self.header.icmp_type = new_type;
self.header.icmp_code = new_code;
}
pub fn compute_checksum(&mut self) {
self.header.crc = 0;
let header_ptr = self.header as *mut Header as usize;
let total_size = Self::get_total_header_size(&self.subheader) + self.payload.len();
let crc = Checksum::compile(unsafe { Checksum::sum(header_ptr, total_size) });
self.header.crc = crc
}
pub fn get_payload(&mut self) -> &mut [u8] {
self.payload
}
pub fn get_total_header_size(subheader: &SubHeader) -> usize {
mem::size_of::<Header>() + subheader.get_size()
}
}
+309
View File
@@ -0,0 +1,309 @@
use error::{Result, Error, PacketError};
use netutils::{Ipv4, Ipv4Header, Checksum, n16};
use netutils;
use packet::{Packet, MutPacket, PacketKind, SubHeader, EchoHeader};
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::fs::File;
use std::io::{Read, Write};
use std::mem;
use std::net::Ipv4Addr;
use syscall::SchemeMut;
use syscall;
//Some reasonable limits, 65k is a waste of memory
const MAX_PACKET_SIZE: usize = 2048;
const MAX_ICMP_PAYLOAD_SIZE: usize = 2000;
enum HandleType {
Echo,
}
struct Handle {
handle_type: HandleType,
events: usize,
flags: usize,
ip_addr: Ipv4Addr,
payload_queue: VecDeque<Vec<u8>>,
}
impl Handle {
pub fn new(handle_type: HandleType, ip_addr: Ipv4Addr, flags: usize) -> Handle {
Handle {
handle_type,
events: 0,
ip_addr,
payload_queue: VecDeque::new(),
flags,
}
}
}
pub struct Icmpd {
icmp_file: File,
scheme_file: File,
next_fd: usize,
echo_ips: BTreeMap<Ipv4Addr, HashSet<usize>>,
handles: BTreeMap<usize, Handle>,
}
impl Icmpd {
pub fn new(icmp_file: File, scheme_file: File) -> Icmpd {
Icmpd {
icmp_file,
scheme_file,
next_fd: 0,
echo_ips: BTreeMap::new(),
handles: BTreeMap::new(),
}
}
pub fn on_scheme_event(&mut self) -> Result<Option<()>> {
loop {
let mut packet = syscall::Packet::default();
if self.scheme_file.read(&mut packet)? == 0 {
break;
}
self.handle(&mut packet);
self.scheme_file.write_all(&packet)?;
}
Ok(None)
}
pub fn on_icmp_packet(&mut self) -> Result<Option<()>> {
let mut packet_buffer = [0; MAX_PACKET_SIZE];
loop {
let bytes_readed =
self.icmp_file
.read(&mut packet_buffer)
.map_err(|e| Error::from_io_error(e, "failed to read a packet from ip:1"))?;
if bytes_readed == 0 {
break;
}
let ip_packet = Ipv4::from_bytes(&packet_buffer[..bytes_readed])
.ok_or_else(|| {
Error::from_parsing_error(PacketError::NotEnoughData,
"failed to parse ip header")
})?;
let icmp_packet =
Packet::from_bytes(&ip_packet.data)
.map_err(|e| Error::from_parsing_error(e, "failed to parse ICMP packet"))?;
match icmp_packet.get_kind() {
PacketKind::EchoRequest => self.on_echo_request(&ip_packet, &icmp_packet)?,
PacketKind::EchoResponse => self.on_echo_response(&ip_packet, &icmp_packet)?,
_ => (),
}
}
Ok(None)
}
fn on_echo_request(&mut self, ip_packet: &Ipv4, icmp_packet: &Packet) -> Result<()> {
let echo_response = produce_icmp_packet(Ipv4Addr::from(ip_packet.header.src.bytes),
PacketKind::EchoResponse,
&SubHeader::None,
icmp_packet.get_payload())?;
self.icmp_file
.write(&echo_response)
.map_err(|e| Error::from_io_error(e, " can't send an echo response packet"))
.map(|_| ())
}
fn on_echo_response(&mut self, ip_packet: &Ipv4, icmp_packet: &Packet) -> Result<()> {
if let SubHeader::Echo(echo_subheader) = *icmp_packet.get_subheader() {
if let Some(fd_set) = self.echo_ips
.get_mut(&Ipv4Addr::from(ip_packet.header.src.bytes)) {
for fd in fd_set.iter() {
if let Some(handle) = self.handles.get_mut(fd) {
if echo_subheader.get_id() == *fd as u16 {
handle
.payload_queue
.push_back(Vec::from(icmp_packet.get_payload()));
if handle.events & syscall::EVENT_READ == syscall::EVENT_READ {
post_fevent(&mut self.scheme_file,
*fd,
syscall::EVENT_READ,
icmp_packet.get_payload().len())?;
}
}
}
}
}
}
Ok(())
}
fn open_echo(&mut self, ip_addr: Ipv4Addr, flags: usize) -> syscall::Result<usize> {
let fd = self.next_fd;
self.next_fd += 1;
let handle = Handle::new(HandleType::Echo, ip_addr, flags);
self.handles.insert(fd, handle);
self.echo_ips
.entry(ip_addr)
.or_insert_with(HashSet::new)
.insert(fd);
Ok(fd)
}
fn read_echo(handle: &mut Handle, buf: &mut [u8]) -> syscall::Result<usize> {
if let Some(payload) = handle.payload_queue.pop_front() {
//TODO replace with a proper memcpy
let mut i = 0;
while i < buf.len() && i < payload.len() {
buf[i] = payload[i];
i += 1;
}
Ok(i)
} else {
Ok(0)
}
}
}
impl SchemeMut for Icmpd {
fn open(&mut self, url: &[u8], flags: usize, _uid: u32, _gid: u32) -> syscall::Result<usize> {
use std::str;
use std::str::FromStr;
let path = str::from_utf8(url)
.or_else(|_| Err(syscall::Error::new(syscall::EINVAL)))?;
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 addr = Ipv4Addr::from_str(addr)
.map_err(|_| syscall::Error::new(syscall::EINVAL))?;
self.open_echo(addr, flags)
}
_ => Err(syscall::Error::new(syscall::EINVAL)),
}
}
fn close(&mut self, fd: usize) -> syscall::Result<usize> {
let (ip, ip_set) = {
let handle = self.handles
.get_mut(&fd)
.ok_or_else(|| syscall::Error::new(syscall::EBADF))?;
match handle.handle_type {
HandleType::Echo => (handle.ip_addr, &mut self.echo_ips),
}
};
self.handles.remove(&fd);
let remove_ip = if let Some(fd_set) = ip_set.get_mut(&ip) {
fd_set.remove(&fd);
fd_set.is_empty()
} else {
false
};
if remove_ip {
ip_set.remove(&ip);
}
Ok(0)
}
fn write(&mut self, fd: usize, buf: &[u8]) -> syscall::Result<usize> {
if buf.len() > MAX_ICMP_PAYLOAD_SIZE {
return Err(syscall::Error::new(syscall::EMSGSIZE));
}
let handle = self.handles
.get_mut(&fd)
.ok_or_else(|| syscall::Error::new(syscall::EBADF))?;
match handle.handle_type {
HandleType::Echo => {
let echo_request = produce_icmp_packet(handle.ip_addr,
PacketKind::EchoRequest,
&SubHeader::Echo(&EchoHeader::new(fd as
u16)),
buf)
.map_err(|_| syscall::Error::new(syscall::EPROTO))?;
self.icmp_file
.write(&echo_request)
.map_err(|_| syscall::Error::new(syscall::EPROTO))
}
}
}
fn read(&mut self, fd: usize, buf: &mut [u8]) -> syscall::Result<usize> {
let handle = self.handles
.get_mut(&fd)
.ok_or_else(|| syscall::Error::new(syscall::EBADF))?;
match handle.handle_type {
HandleType::Echo => Icmpd::read_echo(handle, buf),
}
}
fn fevent(&mut self, fd: usize, events: usize) -> syscall::Result<usize> {
let handle = self.handles
.get_mut(&fd)
.ok_or_else(|| syscall::Error::new(syscall::EBADF))?;
handle.events = events;
Ok(fd)
}
}
fn produce_icmp_packet(to_ip: Ipv4Addr,
kind: PacketKind,
subheader: &SubHeader,
payload: &[u8])
-> Result<Vec<u8>> {
let mut ip_data = vec![0; MutPacket::get_total_header_size(subheader) + payload.len()];
{
let mut out_icmp_packet =
MutPacket::from_bytes(&mut ip_data)
.map_err(|e| Error::from_parsing_error(e, "can't parse empty icmp header"))?;
out_icmp_packet = out_icmp_packet
.set_subheader(subheader)
.map_err(|e| Error::from_parsing_error(e, "can't set subheader"))?;
out_icmp_packet.set_kind(kind);
{
let out_payload = out_icmp_packet.get_payload();
if out_payload.len() != payload.len() {
return Err(Error::from_parsing_error(PacketError::NotEnoughData,
" can't copy icmp payload to echo response"));
}
//WARNING: copy_from_slice can panic if the slices' lengths are different
out_payload.copy_from_slice(payload);
}
out_icmp_packet.compute_checksum();
}
let out_ip_packet = Ipv4 {
header: Ipv4Header {
ver_hlen: 0x45,
services: 0,
len: n16::new((ip_data.len() + mem::size_of::<Ipv4Header>()) as u16),
id: n16::new(0),
flags_fragment: n16::new(0),
ttl: 64,
proto: 1,
checksum: Checksum { data: 0 },
src: netutils::Ipv4Addr::NULL,
dst: netutils::Ipv4Addr { bytes: to_ip.octets() },
},
options: Vec::new(),
data: ip_data,
};
Ok(out_ip_packet.to_bytes())
}
fn post_fevent(scheme_file: &mut File, fd: usize, event: usize, data_len: usize) -> Result<()> {
scheme_file
.write(&syscall::Packet {
id: 0,
pid: 0,
uid: 0,
gid: 0,
a: syscall::number::SYS_FEVENT,
b: fd,
c: event,
d: data_len,
})
.map(|_| ())
.map_err(|e| Error::from_io_error(e, "failed to post fevent"))
}