From 97af7bcef4f527cc552d88075201a6a18baedd80 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Sun, 26 Jun 2016 19:18:38 +0200 Subject: [PATCH] Documentation and code cleanup --- src/device.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++---- src/ethernet.rs | 27 +++++++++++-- src/ip.rs | 31 +++++++++++++++ src/main.rs | 5 ++- src/types.rs | 14 ------- 5 files changed, 153 insertions(+), 27 deletions(-) diff --git a/src/device.rs b/src/device.rs index 890ca23..04e5bc3 100644 --- a/src/device.rs +++ b/src/device.rs @@ -5,8 +5,9 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::io::{Result as IoResult, Error as IoError, Read, Write}; use std::fs; +use std::fmt; -use super::types::{Error, Type}; +use super::types::Error; extern { fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32; @@ -14,14 +15,55 @@ extern { } +/// The type of a tun/tap device +#[derive(RustcDecodable, Debug, Clone, Copy)] +pub enum Type { + /// Tun interface: This interface transports IP packets. + Tun, + /// Tap interface: This insterface transports Ethernet frames. + Tap +} + +impl fmt::Display for Type { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + Type::Tun => write!(formatter, "tun"), + Type::Tap => write!(formatter, "tap"), + } + } +} + + +/// Represents a tun/tap device pub struct Device { fd: fs::File, - ifname: String + ifname: String, + type_: Type, } impl Device { + /// Creates a new tun/tap device + /// + /// This method creates a new device of the `type_` kind with the name `ifname`. + /// + /// The `ifname` must be an interface name not longer than 31 bytes. It can contain the string + /// `%d` which will be replaced with the next free index number that guarantees that the + /// interface name will be free. In this case, the `ifname()` method can be used to obtain the + /// final interface name. + /// + /// # Errors + /// This method will return an error when the underlying system call fails. Common cases are: + /// - The special device file `/dev/net/tun` does not exist or is not accessible by the current + /// user. + /// - The interface name is invalid or already in use. + /// - The current user does not have enough permissions to create tun/tap devices (this + /// requires root permissions). + /// + /// # Panics + /// This method panics if the interface name is longer than 31 bytes. pub fn new(ifname: &str, type_: Type) -> IoResult { let fd = try!(fs::OpenOptions::new().read(true).write(true).open("/dev/net/tun")); + // Add trailing \0 to interface name let mut ifname_string = String::with_capacity(32); ifname_string.push_str(ifname); ifname_string.push('\0'); @@ -33,30 +75,75 @@ impl Device { }; match res { 0 => { + // Remove trailing \0 from name while ifname_c.last() == Some(&0) { ifname_c.pop(); } - Ok(Device{fd: fd, ifname: String::from_utf8(ifname_c).unwrap()}) + Ok(Device{fd: fd, ifname: String::from_utf8(ifname_c).unwrap(), type_: type_}) }, _ => Err(IoError::last_os_error()) } } - #[allow(dead_code)] - pub fn dummy(ifname: &str, path: &str) -> IoResult { - Ok(Device{fd: try!(fs::OpenOptions::new().create(true).read(true).write(true).open(path)), ifname: ifname.to_string()}) - } - + /// Returns the interface name of this device. #[inline] pub fn ifname(&self) -> &str { &self.ifname } + /// Returns the type of this device + #[allow(dead_code)] + #[inline] + pub fn get_type(&self) -> Type { + self.type_ + } + + /// Creates a dummy device based on an existing file + /// + /// This method opens a regular or special file and reads from it to receive packets and + /// writes to it to send packets. This method does not use a networking device and therefore + /// can be used for testing. + /// + /// The parameter `path` is the file that should be used. Special files like `/dev/null`, + /// named pipes and unix sockets can be used with this method. + /// + /// Both `ifname` and `type_` parameters have no effect. + /// + /// # Errors + /// This method will return an error if the file can not be opened for reading and writing. + #[allow(dead_code)] + pub fn dummy(ifname: &str, path: &str, type_: Type) -> IoResult { + Ok(Device{ + fd: try!(fs::OpenOptions::new().create(true).read(true).write(true).open(path)), + ifname: ifname.to_string(), + type_: type_ + }) + } + + /// Reads a packet/frame from the device + /// + /// This method reads one packet or frame (depending on the device type) into the `buffer`. + /// The `buffer` must be large enough to hold a packet/frame of maximum size, otherwise the + /// packet/frame will be split. + /// The method will block until a packet/frame is ready to be read. + /// On success, the method will return the amount of bytes read into the buffer (starting at + /// position 0). + /// + /// # Errors + /// This method will return an error if the underlying read call fails. #[inline] pub fn read(&mut self, mut buffer: &mut [u8]) -> Result { self.fd.read(&mut buffer).map_err(|_| Error::TunTapDevError("Read error")) } + /// Writes a packet/frame to the device + /// + /// This method writes one packet or frame (depending on the device type) from `data` to the + /// device. + /// The method will block until the packet/frame has been written. + /// + /// # Errors + /// This method will return an error if the underlying read call fails. #[inline] pub fn write(&mut self, data: &[u8]) -> Result<(), Error> { match self.fd.write_all(data) { diff --git a/src/ethernet.rs b/src/ethernet.rs index b4ff097..9916649 100644 --- a/src/ethernet.rs +++ b/src/ethernet.rs @@ -11,9 +11,19 @@ use fnv::FnvHasher; use super::types::{Error, Table, Protocol, Address}; use super::util::{now, Time, Duration}; +/// An ethernet frame dissector +/// +/// This dissector is able to extract the source and destination addresses of ethernet frames. +/// +/// If the ethernet frame contains a VLAN tag, both addresses will be prefixed with that tag, +/// resulting in 8-byte addresses. Additional nested tags will be ignored. pub struct Frame; impl Protocol for Frame { + /// Parses an ethernet frame and extracts the source and destination addresses + /// + /// # Errors + /// This method will fail when the given data is not a valid ethernet frame. fn parse(data: &[u8]) -> Result<(Address, Address), Error> { if data.len() < 14 { return Err(Error::ParseError("Frame is too short")); @@ -51,19 +61,27 @@ struct SwitchTableValue { type Hash = BuildHasherDefault; + +/// A table used to implement a learning switch +/// +/// This table is a simple hash map between an address and the destination peer. It learns +/// addresses as they are seen and forgets them after some time. pub struct SwitchTable { + /// The table storing the actual mapping table: HashMap, - cache: Option<(Address, SocketAddr)>, + /// Timeout period for forgetting learnt addresses timeout: Duration } impl SwitchTable { + /// Creates a new switch table pub fn new(timeout: Duration) -> Self { - SwitchTable{table: HashMap::default(), cache: None, timeout: timeout} + SwitchTable{table: HashMap::default(), timeout: timeout} } } impl Table for SwitchTable { + /// Forget addresses that have not been seen for the configured timeout fn housekeep(&mut self) { let now = now(); let mut del: Vec
= Vec::new(); @@ -76,9 +94,9 @@ impl Table for SwitchTable { info!("Forgot address {}", key); self.table.remove(&key); } - self.cache = None; } + /// Learns the given address, inserting it in the hash map #[inline] fn learn(&mut self, key: Address, _prefix_len: Option, addr: SocketAddr) { let value = SwitchTableValue{address: addr, timeout: now()+self.timeout as Time}; @@ -87,6 +105,7 @@ impl Table for SwitchTable { } } + /// Retrieves a peer for an address if it is inside the hash map #[inline] fn lookup(&mut self, key: &Address) -> Option { match self.table.get(key) { @@ -95,11 +114,13 @@ impl Table for SwitchTable { } } + /// Removes an address from the map and returns whether something has been removed #[inline] fn remove(&mut self, key: &Address) -> bool { self.table.remove(key).is_some() } + /// Removed all addresses associated with a certain peer fn remove_all(&mut self, addr: &SocketAddr) { let mut remove = Vec::new(); for (key, val) in &self.table { diff --git a/src/ip.rs b/src/ip.rs index 33d9625..fdad922 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -11,10 +11,18 @@ use fnv::FnvHasher; use super::types::{Protocol, Error, Table, Address}; +/// An IP packet dissector +/// +/// This dissector is able to extract the source and destination ip addresses of ipv4 packets and +/// ipv6 packets. #[allow(dead_code)] pub struct Packet; impl Protocol for Packet { + /// Parses an ip packet and extracts the source and destination addresses + /// + /// # Errors + /// This method will fail when the given data is not a valid ipv4 and ipv6 packet. fn parse(data: &[u8]) -> Result<(Address, Address), Error> { if data.len() < 1 { return Err(Error::ParseError("Empty header")); @@ -51,39 +59,56 @@ struct RoutingEntry { type Hash = BuildHasherDefault; +/// A prefix-based routing table +/// +/// This table contains a mapping of prefixes associated with peer addresses. +/// To speed up lookup, prefixes are grouped into full bytes and map to a list of prefixes with +/// more fine grained prefixes. pub struct RoutingTable(HashMap, Vec, Hash>); impl RoutingTable { + /// Creates a new empty routing table pub fn new() -> Self { RoutingTable(HashMap::default()) } } impl Table for RoutingTable { + /// Learns the given address, inserting it in the hash map fn learn(&mut self, addr: Address, prefix_len: Option, address: SocketAddr) { + // If prefix length is not set, treat the whole addess as significant let prefix_len = match prefix_len { Some(val) => val, None => addr.len * 8 }; info!("New routing entry: {}/{} => {}", addr, prefix_len, address); + // Round the prefix length down to the next multiple of 8 and extraxt a prefix of that + // length. let group_len = prefix_len as usize / 8; assert!(group_len <= 16); let mut group_bytes = Vec::with_capacity(group_len); group_bytes.extend_from_slice(&addr.data[0..group_len]); + // Create an entry let routing_entry = RoutingEntry{address: address, bytes: addr.data, prefix_len: prefix_len}; + // Add the entry to the routing table, creating a new list of the prefix group is empty. match self.0.entry(group_bytes) { hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry), hash_map::Entry::Vacant(entry) => { entry.insert(vec![routing_entry]); () } } } + /// Retrieves a peer for an address if it is inside the routing table fn lookup(&mut self, addr: &Address) -> Option { let len = addr.len as usize; let mut found = None; let mut found_len: isize = -1; + // Iterate over the prefix length from longest prefix group to shortest (empty) prefix + // group for i in 0..len+1 { if let Some(group) = self.0.get(&addr.data[0..len-i]) { + // If the group is not empty, check every entry for entry in group { + // Calculate the match length of the address and the prefix let mut match_len = 0; for j in 0..addr.len as usize { let b = addr.data[j] ^ entry.bytes[j]; @@ -94,6 +119,8 @@ impl Table for RoutingTable { break; } } + // If the full prefix matches and the match is longer than the longest prefix + // found so far, remember the peer if match_len as u8 >= entry.prefix_len && match_len as isize > found_len { found = Some(entry.address); found_len = match_len as isize; @@ -101,13 +128,16 @@ impl Table for RoutingTable { } } } + // Return the longest match found (if any). found } + /// This method does not do anything. fn housekeep(&mut self) { //nothing to do } + /// Removes an address from the map and returns whether something has been removed #[inline] fn remove(&mut self, addr: &Address) -> bool { let len = addr.len as usize; @@ -139,6 +169,7 @@ impl Table for RoutingTable { found } + /// Removed all addresses associated with a certain peer fn remove_all(&mut self, addr: &SocketAddr) { for (_key, entry) in &mut self.0 { entry.retain(|entr| &entr.address != addr); diff --git a/src/main.rs b/src/main.rs index a3daef0..e205825 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ // This software is licensed under GPL-3 or newer (see LICENSE.md) #![cfg_attr(feature = "bench", feature(test))] + #[macro_use] extern crate log; extern crate time; extern crate docopt; @@ -34,10 +35,10 @@ use std::hash::{Hash, SipHasher, Hasher}; use std::str::FromStr; use std::process::Command; -use device::Device; +use device::{Device, Type}; use ethernet::SwitchTable; use ip::RoutingTable; -use types::{Mode, Type, Range, Table, Protocol}; +use types::{Mode, Range, Table, Protocol}; use cloud::GenericCloud; use udpmessage::VERSION; use crypto::{Crypto, CryptoMethod}; diff --git a/src/types.rs b/src/types.rs index e4c20ab..b460e82 100644 --- a/src/types.rs +++ b/src/types.rs @@ -192,20 +192,6 @@ impl fmt::Debug for Range { } -#[derive(RustcDecodable, Debug, Clone, Copy)] -pub enum Type { - Tun, Tap -} -impl fmt::Display for Type { - fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - Type::Tun => write!(formatter, "tun"), - Type::Tap => write!(formatter, "tap"), - } - } -} - - #[derive(RustcDecodable, Debug, Clone, Copy)] pub enum Mode { Normal, Hub, Switch, Router