From 69bb8fb80bf50b183cb2c8bc80b89bd8ff9bf29c Mon Sep 17 00:00:00 2001 From: Egor Karavaev Date: Sun, 11 Jun 2017 00:53:30 +0300 Subject: [PATCH] Add icmpd daemon, only Echo reply for now. --- Cargo.toml | 6 ++- src/icmpd/error.rs | 80 ++++++++++++++++++++++++++++++ src/icmpd/main.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++ src/icmpd/packet.rs | 100 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/icmpd/error.rs create mode 100644 src/icmpd/main.rs create mode 100644 src/icmpd/packet.rs diff --git a/Cargo.toml b/Cargo.toml index 8440d9f3c0..c29a186b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = { path = "../event" } redox_syscall = "0.1" diff --git a/src/icmpd/error.rs b/src/icmpd/error.rs new file mode 100644 index 0000000000..cc939b6072 --- /dev/null +++ b/src/icmpd/error.rs @@ -0,0 +1,80 @@ +use std::result; +use std::fmt; +use syscall::error::Error as SyscallError; +use std::io::Error as IOError; +use std::convert; + +pub enum ParsingError { + NotEnoughData, + IncorrectChecksum, +} + +enum ErrorType { + Syscall(SyscallError), + IOError(IOError), + ParsingError(ParsingError), +} + +pub struct Error { + error_type: ErrorType, + descr: String, +} + +impl Error { + pub fn from_parsing_error>(parsing_error: ParsingError, descr: S) -> Error { + Error { + error_type: ErrorType::ParsingError(parsing_error), + descr: descr.into(), + } + } + pub fn from_syscall_error>(syscall_error: SyscallError, descr: S) -> Error { + Error { + error_type: ErrorType::Syscall(syscall_error), + descr: descr.into(), + } + } + + pub fn from_io_error>(io_error: IOError, descr: S) -> Error { + Error { + error_type: ErrorType::IOError(io_error), + descr: descr.into(), + } + } +} + +impl fmt::Display for ParsingError { + fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { + write!(f, "{}", match *self { + ParsingError::NotEnoughData => "not enough data", + ParsingError::IncorrectChecksum => "checksum error", + }) + } +} + +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::ParsingError(ref parsign_error) => { + write!(f, + "{} : packet parsing error : {}", + self.descr, + parsign_error) + } + } + } +} + +impl convert::From for Error { + fn from(e: IOError) -> Self { + Error::from_io_error(e, "") + } +} + +pub type Result = result::Result; +pub type ParsingResult = result::Result; diff --git a/src/icmpd/main.rs b/src/icmpd/main.rs new file mode 100644 index 0000000000..4e7c582876 --- /dev/null +++ b/src/icmpd/main.rs @@ -0,0 +1,115 @@ +extern crate event; +extern crate syscall; +extern crate netutils; + +use error::{Result, Error, ParsingError}; +use event::EventQueue; +use netutils::{Ipv4, Ipv4Header, Checksum, n16}; +use packet::{Packet, MutPacket}; +use std::fs::File; +use std::io::{Read, Write}; +use std::os::unix::io::{RawFd, FromRawFd}; +use std::process; +use std::mem; + +mod error; +mod packet; + +const MAX_PACKET_SIZE: usize = 2048; + +fn do_echo_response(in_ip_packet: &Ipv4, + in_icmp_packet: &Packet, + icmp_file: &mut File) + -> Result<()> { + let mut ip_data = vec![0; in_icmp_packet.get_total_data_size()]; + { + 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.set_echo_response(); + { + let payload = out_icmp_packet.get_payload(); + let in_payload = in_icmp_packet.get_payload(); + if payload.len() != in_payload.len() { + return Err(Error::from_parsing_error(ParsingError::NotEnoughData, + " can't copy icmp payload to echo response")); + } + //WARNING: copy_from_slice can panic if the slices' lengths are different + payload.copy_from_slice(in_icmp_packet.get_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::()) as u16), + id: n16::new(0), + flags_fragment: n16::new(0), + ttl: in_ip_packet.header.ttl, + proto: 1, + checksum: Checksum { data: 0 }, + src: in_ip_packet.header.dst, + dst: in_ip_packet.header.src, + }, + options: Vec::new(), + data: ip_data, + }; + icmp_file + .write(&out_ip_packet.to_bytes()) + .map_err(|e| Error::from_io_error(e, " can't send an echo response packet")) + .map(|_| ()) +} + +fn on_icmp_packet(icmp_file: &mut File) -> Result> { + let mut packet_buffer = [0; MAX_PACKET_SIZE]; + loop { + let bytes_readed = + 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(Error::from_parsing_error(ParsingError::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"))?; + + if icmp_packet.is_echo_request() { + do_echo_response(&ip_packet, &icmp_packet, icmp_file)?; + } + } + Ok(None) +} + +fn run() -> Result<()> { + use syscall::flag::*; + + let icmp_fd = syscall::open("ip:1", O_RDWR | O_NONBLOCK) + .map_err(|e| Error::from_syscall_error(e, "failed to open ip:1"))?; + + if unsafe { syscall::clone(0).unwrap() } != 0 { + return Ok(()); + } + + let mut event_queue = + EventQueue::<(), Error>::new() + .map_err(|e| Error::from_io_error(e, "failed to create event queue"))?; + let mut icmp_file = unsafe { File::from_raw_fd(icmp_fd as RawFd) }; + event_queue + .add(icmp_fd as RawFd, + move |_fd| -> Result> { on_icmp_packet(&mut icmp_file) }) + .map_err(|e| Error::from_io_error(e, "failed to listen to events on ip:1"))?; + event_queue.run() +} + +fn main() { + match run() { + Err(err) => println!("icmpd: {}", err), + _ => {} + } + process::exit(0); +} diff --git a/src/icmpd/packet.rs b/src/icmpd/packet.rs new file mode 100644 index 0000000000..5dddb80e6e --- /dev/null +++ b/src/icmpd/packet.rs @@ -0,0 +1,100 @@ +use error::{ParsingResult, ParsingError}; +use netutils::Checksum; +use std::mem; + +#[repr(packed)] +pub struct Header { + icmp_type: u8, + icmp_code: u8, + crc: u16, +} + +pub struct Packet<'a> { + header: &'a Header, + payload: &'a [u8], +} + +pub struct MutPacket<'a> { + header: &'a mut Header, + payload: &'a mut [u8], +} + +impl<'a> Packet<'a> { + pub fn from_bytes<'b>(bytes: &'b [u8]) -> ParsingResult> + where 'b: 'a + { + if bytes.len() < mem::size_of::
() { + Err(ParsingError::NotEnoughData) + } else { + let (header_bytes, payload_bytes) = bytes.split_at(mem::size_of::
()); + let packet = Packet { + header: unsafe { mem::transmute(header_bytes.as_ptr()) }, + payload: payload_bytes, + }; + if packet.is_checksum_ok() { + Ok(packet) + } else { + Err(ParsingError::IncorrectChecksum) + } + } + } + + 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 is_echo_request(&self) -> bool { + self.header.icmp_type == ECHO_REQUEST_TYPE && self.header.icmp_code == ECHO_REQUEST_CODE + } + + pub fn get_payload(&self) -> &[u8] { + self.payload + } + + pub fn get_total_data_size(&self) -> usize { + mem::size_of::
() + self.payload.len() + } +} + +impl<'a> MutPacket<'a> { + pub fn from_bytes<'b>(bytes: &'b mut [u8]) -> ParsingResult> + where 'b: 'a + { + if bytes.len() < mem::size_of::
() { + Err(ParsingError::NotEnoughData) + } else { + let (header_bytes, payload_bytes) = bytes.split_at_mut(mem::size_of::
()); + Ok(MutPacket { + header: unsafe { mem::transmute(header_bytes.as_ptr()) }, + payload: payload_bytes, + }) + } + } + + pub fn set_echo_response(&mut self) { + self.header.icmp_type = ECHO_RESPONSE_TYPE; + self.header.icmp_code = ECHO_RESPONSE_CODE; + } + + pub fn compute_checksum(&mut self) { + self.header.crc = 0; + let header_ptr = self.header as *mut Header as usize; + let total_size = mem::size_of::
() + 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 + } +} + +const ECHO_REQUEST_TYPE: u8 = 8; +const ECHO_REQUEST_CODE: u8 = 0; +const ECHO_RESPONSE_TYPE: u8 = 0; +const ECHO_RESPONSE_CODE: u8 = 0;