From 8301ee85a1fc81ffee14c056f0fd341ec819143b Mon Sep 17 00:00:00 2001 From: Egor Karavaev Date: Mon, 12 Jun 2017 00:23:04 +0300 Subject: [PATCH] Add initial support for icmp scheme. --- src/icmpd/main.rs | 108 ++++------------- src/icmpd/packet.rs | 38 +++++- src/icmpd/scheme.rs | 288 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+), 87 deletions(-) create mode 100644 src/icmpd/scheme.rs diff --git a/src/icmpd/main.rs b/src/icmpd/main.rs index 4e7c582876..2623547228 100644 --- a/src/icmpd/main.rs +++ b/src/icmpd/main.rs @@ -2,114 +2,58 @@ extern crate event; extern crate syscall; extern crate netutils; -use error::{Result, Error, ParsingError}; +use error::{Result, Error}; use event::EventQueue; -use netutils::{Ipv4, Ipv4Header, Checksum, n16}; -use packet::{Packet, MutPacket}; +use scheme::Icmpd; +use std::cell::RefCell; use std::fs::File; -use std::io::{Read, Write}; use std::os::unix::io::{RawFd, FromRawFd}; use std::process; -use std::mem; +use std::rc::Rc; 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) -} +mod scheme; 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"))?; + .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; if unsafe { syscall::clone(0).unwrap() } != 0 { return Ok(()); } + 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 mut icmp_file = unsafe { File::from_raw_fd(icmp_fd as RawFd) }; + + let icmpd_ = icmpd.clone(); + event_queue - .add(icmp_fd as RawFd, - move |_fd| -> Result> { on_icmp_packet(&mut icmp_file) }) + .add(icmp_fd, move |_fd| icmpd_.borrow_mut().on_icmp_packet()) .map_err(|e| Error::from_io_error(e, "failed to listen to events on ip:1"))?; + + event_queue + .add(scheme_fd, move |_fd| icmpd.borrow_mut().on_scheme_event()) + .map_err(|e| Error::from_io_error(e, "failed to listen to events on icmp"))?; + event_queue.run() } fn main() { - match run() { - Err(err) => println!("icmpd: {}", err), - _ => {} + if let Err(err) = run() { + println!("icmpd: {}", err); + process::exit(1); } process::exit(0); } diff --git a/src/icmpd/packet.rs b/src/icmpd/packet.rs index 5dddb80e6e..fb35ce745a 100644 --- a/src/icmpd/packet.rs +++ b/src/icmpd/packet.rs @@ -19,6 +19,15 @@ pub struct MutPacket<'a> { payload: &'a mut [u8], } +pub enum PacketKind { + EchoRequest, + EchoResponse, + HostUnreachable, + PortUnreachable, + ProtoUnreachable, + Unknown, +} + impl<'a> Packet<'a> { pub fn from_bytes<'b>(bytes: &'b [u8]) -> ParsingResult> where 'b: 'a @@ -48,8 +57,15 @@ impl<'a> Packet<'a> { 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_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] { @@ -76,9 +92,17 @@ impl<'a> MutPacket<'a> { } } - pub fn set_echo_response(&mut self) { - self.header.icmp_type = ECHO_RESPONSE_TYPE; - self.header.icmp_code = ECHO_RESPONSE_CODE; + pub fn set_kind(&mut self, packet_type: PacketKind) { + let (new_type, new_code) = match packet_type { + EchoRequest => (ECHO_REQUEST_TYPE, ECHO_REQUEST_CODE), + EchoResponse => (ECHO_RESPONSE_TYPE, ECHO_RESPONSE_CODE), + HostUnreachable => (UNREACHABLE_TYPE, UNREACHABLE_HOST_CODE), + PortUnreachable => (UNREACHABLE_TYPE, UNREACHABLE_PORT_CODE), + ProtoUnreachable => (UNREACHABLE_TYPE, UNREACHABLE_PROTO_CODE), + 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) { @@ -98,3 +122,7 @@ 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; diff --git a/src/icmpd/scheme.rs b/src/icmpd/scheme.rs new file mode 100644 index 0000000000..22616f6f67 --- /dev/null +++ b/src/icmpd/scheme.rs @@ -0,0 +1,288 @@ +use error::{Result, Error, ParsingError}; +use netutils::{Ipv4, Ipv4Header, Checksum, n16}; +use netutils; +use packet::{Header, Packet, MutPacket, PacketKind}; +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>, +} + +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>, + handles: BTreeMap, +} + +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> { + loop { + let mut packet = syscall::Packet::default(); + if self.scheme_file.read(&mut packet)? == 0 { + break; + } + self.handle(&mut packet); + } + Ok(None) + } + + pub fn on_icmp_packet(&mut self) -> Result> { + 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(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"))?; + + 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, + 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 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) { + handle + .payload_queue + .push_back(Vec::from(icmp_packet.get_payload())); + 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 { + 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 { + 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 { + use std::str; + use std::str::FromStr; + + // if uid != 0 { + // return Err(syscall::Error::new(syscall::EACCES)); + // } + + let path = str::from_utf8(url) + .or(Err(syscall::Error::new(syscall::EINVAL)))?; + let mut parts = path.split("/"); + let method = parts.next().ok_or(syscall::Error::new(syscall::EINVAL))?; + match method { + "echo" => { + let addr = parts.next().ok_or(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 { + let (ip, ip_set) = { + let handle = self.handles + .get_mut(&fd) + .ok_or(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 { + if buf.len() > MAX_ICMP_PAYLOAD_SIZE { + return Err(syscall::Error::new(syscall::EMSGSIZE)); + } + let handle = self.handles + .get_mut(&fd) + .ok_or(syscall::Error::new(syscall::EBADF))?; + match handle.handle_type { + HandleType::Echo => { + let echo_request = + produce_icmp_packet(handle.ip_addr, PacketKind::EchoRequest, 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 { + let handle = self.handles + .get_mut(&fd) + .ok_or(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 { + let handle = self.handles + .get_mut(&fd) + .ok_or(syscall::Error::new(syscall::EBADF))?; + handle.events = events; + Ok(fd) + } +} + +fn produce_icmp_packet(to_ip: Ipv4Addr, kind: PacketKind, payload: &[u8]) -> Result> { + let mut ip_data = vec![0; mem::size_of::
() + 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.set_kind(kind); + { + let out_payload = out_icmp_packet.get_payload(); + if out_payload.len() != 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 + 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::()) 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")) +}