diff --git a/src/benches.rs b/src/benches.rs index 4ad5e78..a79f44c 100644 --- a/src/benches.rs +++ b/src/benches.rs @@ -9,7 +9,7 @@ use std::net::{UdpSocket, ToSocketAddrs, Ipv4Addr, SocketAddr, SocketAddrV4}; use std::os::unix::io::AsRawFd; use super::cloud::GenericCloud; -use super::device::Device; +use super::device::{Device, Type}; use super::udpmessage::{Options, Message, encode, decode}; use super::crypto::{Crypto, CryptoMethod}; use super::ethernet::{Frame, SwitchTable}; @@ -155,7 +155,7 @@ fn epoll_wait(b: &mut Bencher) { #[bench] fn handle_interface_data(b: &mut Bencher) { let mut node = GenericCloud::::new( - Device::dummy("vpncloud0", "/dev/null").unwrap(), 0, None, + Device::dummy("vpncloud0", "/dev/null", Type::Tap).unwrap(), 0, None, Box::new(SwitchTable::new(300)), 1800, true, true, vec![], Crypto::None ); let mut data = [0; 1500]; @@ -169,7 +169,7 @@ fn handle_interface_data(b: &mut Bencher) { #[bench] fn handle_net_message(b: &mut Bencher) { let mut node = GenericCloud::::new( - Device::dummy("vpncloud0", "/dev/null").unwrap(), 0, None, + Device::dummy("vpncloud0", "/dev/null", Type::Tap).unwrap(), 0, None, Box::new(SwitchTable::new(300)), 1800, true, true, vec![], Crypto::None ); let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1)); diff --git a/src/cloud.rs b/src/cloud.rs index a2f20a3..b97e0ae 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -11,6 +11,7 @@ use std::os::unix::io::AsRawFd; use std::marker::PhantomData; use std::hash::BuildHasherDefault; use std::time::Instant; +use std::cmp::{min, max}; use fnv::FnvHasher; use epoll; @@ -23,7 +24,7 @@ use super::types::{Table, Protocol, Range, Error, NetworkId, NodeId}; use super::device::Device; use super::udpmessage::{encode, decode, Options, Message}; use super::crypto::Crypto; -use super::util::{now, Time, Duration}; +use super::util::{now, Time, Duration, resolve}; type Hash = BuildHasherDefault; @@ -72,8 +73,7 @@ impl PeerList { #[inline] fn is_connected(&self, addr: Addr) -> Result { - let addrs = try!(addr.to_socket_addrs().map_err(|_| Error::SocketError("Error looking up name"))); - for addr in addrs { + for addr in try!(resolve(addr)) { if self.contains_addr(&addr) { return Ok(true); } @@ -219,9 +219,16 @@ impl GenericCloud

{ self.device.ifname() } + /// Sends the message to all peers + /// + /// # Errors + /// Returns an `Error::SocketError` when the underlying system call fails or only part of the + /// message could be sent (can this even happen?). + /// Some messages could have been sent. #[inline] fn broadcast_msg(&mut self, msg: &mut Message) -> Result<(), Error> { debug!("Broadcasting {:?}", msg); + // Encrypt and encode once and send several times let msg_data = encode(&self.options, msg, &mut self.buffer_out, &mut self.crypto); for addr in self.peers.as_vec() { let socket = match addr { @@ -240,9 +247,15 @@ impl GenericCloud

{ Ok(()) } + /// Sends a message to one peer + /// + /// # Errors + /// Returns an `Error::SocketError` when the underlying system call fails or only part of the + /// message could be sent (can this even happen?). #[inline] fn send_msg(&mut self, addr: SocketAddr, msg: &mut Message) -> Result<(), Error> { debug!("Sending {:?} to {}", msg, addr); + // Encrypt and encode let msg_data = encode(&self.options, msg, &mut self.buffer_out, &mut self.crypto); let socket = match addr { SocketAddr::V4(_) => &self.socket4, @@ -258,16 +271,28 @@ impl GenericCloud

{ } } + /// Returns the self-perceived addresses (IPv4 and IPv6) of this node + /// + /// Note that those addresses could be private addresses that are not reachable by other nodes, + /// or only some other nodes inside the same network. + /// + /// # Errors + /// Returns an IOError if the underlying system call fails #[allow(dead_code)] pub fn address(&self) -> IoResult<(SocketAddr, SocketAddr)> { Ok((try!(self.socket4.local_addr()), try!(self.socket6.local_addr()))) } + /// Returns the number of peers #[allow(dead_code)] pub fn peer_count(&self) -> usize { self.peers.len() } + /// Adds a peer to the reconnect list + /// + /// This method adds a peer to the list of nodes to reconnect to. A periodic task will try to + /// connect to the peer if it is not already connected. pub fn add_reconnect_peer(&mut self, add: String) { self.reconnect_peers.push(ReconnectEntry { address: add, @@ -277,9 +302,13 @@ impl GenericCloud

{ }) } + /// Returns whether the address is blacklisted + /// + /// # Errors + /// Returns an `Error::SocketError` if the given address is a name that failed to resolve to + /// actual addresses. fn is_blacklisted(&self, addr: Addr) -> Result { - let addrs = try!(addr.to_socket_addrs().map_err(|_| Error::SocketError("Error looking up name"))); - for addr in addrs { + for addr in try!(resolve(addr)) { if self.blacklist_peers.contains(&addr) { return Ok(true); } @@ -287,6 +316,14 @@ impl GenericCloud

{ Ok(false) } + /// Connects to a node given by its address + /// + /// This method connects to node by sending a `Message::Init` to it. If `addr` is a name that + /// resolves to multiple addresses, one message is sent to each of them. + /// If the node is already a connected peer or the address is blacklisted, no message is sent. + /// + /// # Errors + /// This method returns `Error::NameError` if the address is a name that fails to resolve. pub fn connect(&mut self, addr: Addr) -> Result<(), Error> { if try!(self.peers.is_connected(addr.clone())) || try!(self.is_blacklisted(addr.clone())) { return Ok(()) @@ -294,39 +331,48 @@ impl GenericCloud

{ debug!("Connecting to {}", addr); let subnets = self.addresses.clone(); let node_id = self.node_id; - let mut msg = Message::Init(0, node_id, subnets); - if let Ok(addrs) = addr.to_socket_addrs() { - let mut addrs = addrs.collect::>(); - addrs.dedup(); - for a in addrs { - //Ignore error this time - self.send_msg(a, &mut msg).ok(); - } + // Send a message to each resolved address + for a in try!(resolve(addr)) { + // Ignore error this time + let mut msg = Message::Init(0, node_id, subnets.clone()); + self.send_msg(a, &mut msg).ok(); } Ok(()) } + /// Run all periodic housekeeping tasks + /// + /// This method executes several tasks: + /// - Remove peers that have timed out + /// - Remove switch table entries that have timed out + /// - Periodically send the peers list to all peers + /// - Periodically reconnect to peers in the reconnect list + /// + /// # Errors + /// This method returns errors if sending a message fails or resolving an address fails. fn housekeep(&mut self) -> Result<(), Error> { self.peers.timeout(); self.table.housekeep(); + // Periodically send peer list to peers let now = now(); if self.next_peerlist <= now { debug!("Send peer list to all peers"); let mut peer_num = self.peers.len(); + // If the number of peers is high, send only a fraction of the full peer list to + // reduce the management traffic. The number of peers to send is the square root of the + // total number of peers. if peer_num > 10 { - peer_num = (peer_num as f32).sqrt().ceil() as usize; - if peer_num < 10 { - peer_num = 10; - } - if peer_num > 255 { - peer_num = 255 - } + peer_num = max(10, min(255, (peer_num as f32).sqrt().ceil() as usize)); } + // Select that many peers... let peers = self.peers.subset(peer_num); + // ...and send them to all peers let mut msg = Message::Peers(peers); try!(self.broadcast_msg(&mut msg)); + // Reschedule for next update self.next_peerlist = now + self.update_freq as Time; } + // Connect to those reconnect_peers that are due for entry in self.reconnect_peers.clone() { if entry.next > now { continue @@ -334,54 +380,114 @@ impl GenericCloud

{ try!(self.connect(&entry.address as &str)); } for entry in &mut self.reconnect_peers { + // Schedule for next second if node is connected if try!(self.peers.is_connected(&entry.address as &str)) { entry.tries = 0; entry.timeout = 1; entry.next = now + 1; continue } + // Ignore if next attempt is already in the future if entry.next > now { continue } + // Exponential backoff: every 10 tries, the interval doubles entry.tries += 1; if entry.tries > 10 { entry.tries = 0; entry.timeout *= 2; } + // Maximum interval is one hour if entry.timeout > 3600 { entry.timeout = 3600; } + // Schedule next connection attempt entry.next = now + entry.timeout as Time; } Ok(()) } + /// Handles payload data coming in from the local network device + /// + /// This method takes payload data received from the local device and parses it to obtain the + /// destination address. Then it checks the lookup table to get the peer for that destination + /// address. If a peer is found, the message is sent to it, otherwise the message is either + /// broadcast to all peers or dropped (depending on mode). + /// + /// The parameter `payload` contains the payload data starting at position `start` and ending + /// at `end`. It is important that the buffer has enough space before the payload data to + /// prepend a header of max 64 bytes and enough space after the payload data to append a mac of + /// max 64 bytes. + /// + /// # Errors + /// This method fails + /// - with `Error::ParseError` if the payload data failed to parse + /// - with `Error::SocketError` if sending a message fails pub fn handle_interface_data(&mut self, payload: &mut [u8], start: usize, end: usize) -> Result<(), Error> { let (src, dst) = try!(P::parse(&payload[start..end])); debug!("Read data from interface: src: {}, dst: {}, {} bytes", src, dst, end-start); match self.table.lookup(&dst) { - Some(addr) => { + Some(addr) => { // Peer found for destination debug!("Found destination for {} => {}", dst, addr); try!(self.send_msg(addr, &mut Message::Data(payload, start, end))); if !self.peers.contains_addr(&addr) { + // If the peer is not actually conected, remove the entry in the table and try + // to reconnect. warn!("Destination for {} not found in peers: {}", dst, addr); self.table.remove(&dst); try!(self.connect(&addr)); } }, None => { - if !self.broadcast { + if self.broadcast { + debug!("No destination for {} found, broadcasting", dst); + let mut msg = Message::Data(payload, start, end); + try!(self.broadcast_msg(&mut msg)); + } else { debug!("No destination for {} found, dropping", dst); - return Ok(()); } - debug!("No destination for {} found, broadcasting", dst); - let mut msg = Message::Data(payload, start, end); - try!(self.broadcast_msg(&mut msg)); } } Ok(()) } + /// Handles a message received from the network + /// + /// This method handles messages from the network, i.e. from peers. `peer` contains the sender + /// of the message. `options` contains the options from the message and `msg` contains the + /// message. + /// + /// If the `network_id` in the messages options differs from the `network_id` of this node, + /// the message is simply ignored. + /// + /// Then this method will check the message type and will handle each message type differently. + /// + /// # `Message::Data` messages + /// This message type contains payload data and therefore this path is optimized for speed. + /// + /// The payload of data messages is written to the local network device and if the node is in + /// a learning mode it will associate the sender peer with the source address. + /// + /// # `Message::Peers` messages + /// If this message is received, the local node will use all the node addresses in the message + /// as well as the senders address to connect to. + /// + /// # `Message::Init` messages + /// This message is used in the peer connection handshake. + /// + /// To make sure, the node does not connect to itself, it will compare the remote `node_id` to + /// the local one. If the id is the same, it will ignore the message and blacklist the address + /// so that it won't be used in the future. + /// + /// If the message is coming from a different node, the nodes address is added to the peer list + /// and its claimed addresses are associated with it. + /// + /// If the `stage` of the message is 1, a `Message::Init` message with `stage=1` is sent in + /// reply, together with a peer list. + /// + /// # `Message::Close` message + /// If this message is received, the sender is removed from the peer list and its claimed + /// addresses are removed from the table. pub fn handle_net_message(&mut self, peer: SocketAddr, options: Options, msg: Message) -> Result<(), Error> { if self.options.network_id != options.network_id { info!("Ignoring message from {} with wrong token {:?}", peer, options.network_id); @@ -399,16 +505,18 @@ impl GenericCloud

{ return Err(Error::TunTapDevError("Failed to write to device")); } } - // not adding peer to increase performance if self.learning { - //learn single address + // Learn single address self.table.learn(src, None, peer); } + // Not adding peer in this case to increase performance }, Message::Peers(peers) => { + // Connect to sender if not connected if !self.peers.contains_addr(&peer) { try!(self.connect(&peer)); } + // Connect to all peers in the message for p in &peers { if ! self.peers.contains_addr(p) && ! self.blacklist_peers.contains(p) { try!(self.connect(p)); @@ -416,10 +524,12 @@ impl GenericCloud

{ } }, Message::Init(stage, node_id, ranges) => { + // Avoid connecting to self if node_id == self.node_id { self.blacklist_peers.push(peer); return Ok(()) } + // Add sender as peer or as alternative address to existing peer if self.peers.contains_node(&node_id) { self.peers.add_alt_addr(node_id, peer); } else { @@ -428,6 +538,7 @@ impl GenericCloud

{ self.table.learn(range.base, Some(range.prefix_len), peer); } } + // Reply with stage=1 if stage is 0 if stage == 0 { let peers = self.peers.as_vec(); let own_addrs = self.addresses.clone(); @@ -444,6 +555,13 @@ impl GenericCloud

{ Ok(()) } + /// The main method of the node + /// + /// This method will use epoll to wait in the sockets and the device at the same time. + /// It will read from the sockets, decode and decrypt the message and then call the + /// `handle_net_message` method. It will also read from the device and call + /// `handle_interface_data` for each packet read. + /// Also, this method will call `housekeep` every second. #[allow(unknown_lints)] #[allow(cyclomatic_complexity)] pub fn run(&mut self) { diff --git a/src/ip.rs b/src/ip.rs index 80ef5bb..e734001 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -64,6 +64,7 @@ type Hash = BuildHasherDefault; /// 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. +#[derive(Default)] pub struct RoutingTable(HashMap, Vec, Hash>); impl RoutingTable { diff --git a/src/main.rs b/src/main.rs index e205825..86be4ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,14 +18,14 @@ extern crate fnv; extern crate net2; #[cfg(feature = "bench")] extern crate test; -#[macro_use] mod util; -mod types; -mod crypto; -mod udpmessage; -mod ethernet; -mod ip; -mod cloud; -mod device; +#[macro_use] pub mod util; +pub mod types; +pub mod crypto; +pub mod udpmessage; +pub mod ethernet; +pub mod ip; +pub mod cloud; +pub mod device; #[cfg(test)] mod tests; #[cfg(feature = "bench")] mod benches; diff --git a/src/types.rs b/src/types.rs index b460e82..8b8cb49 100644 --- a/src/types.rs +++ b/src/types.rs @@ -224,6 +224,7 @@ pub enum Error { ParseError(&'static str), WrongNetwork(Option), SocketError(&'static str), + NameError(String), TunTapDevError(&'static str), CryptoError(&'static str) } @@ -234,6 +235,7 @@ impl fmt::Display for Error { Error::SocketError(ref msg) => write!(formatter, "{}", msg), Error::TunTapDevError(ref msg) => write!(formatter, "{}", msg), Error::CryptoError(ref msg) => write!(formatter, "{}", msg), + Error::NameError(ref name) => write!(formatter, "failed to resolve name '{}'", name), Error::WrongNetwork(Some(net)) => write!(formatter, "wrong network id: {}", net), Error::WrongNetwork(None) => write!(formatter, "wrong network id: none"), } diff --git a/src/util.rs b/src/util.rs index f0bc745..24e3667 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,6 +2,11 @@ // Copyright (C) 2015-2016 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) +use std::net::{SocketAddr, ToSocketAddrs}; +use std::fmt; + +use super::types::Error; + #[cfg(target_os = "linux")] use libc; @@ -102,3 +107,12 @@ macro_rules! try_fail { } } ); } + + +pub fn resolve(addr: Addr) -> Result, Error> { + let addrs = try!(addr.to_socket_addrs().map_err(|_| Error::NameError(format!("{}", addr)))); + // Remove duplicates in addrs (why are there duplicates???) + let mut addrs = addrs.collect::>(); + addrs.dedup(); + Ok(addrs) +} diff --git a/vpncloud.md b/vpncloud.md index d6dfbbd..2c6412a 100644 --- a/vpncloud.md +++ b/vpncloud.md @@ -70,7 +70,7 @@ vpncloud(1) -- Peer-to-peer VPN Switch table entry timeout in seconds. This parameter is only used in switch mode. Addresses that have not been seen for the given period of time will - be forgot. [default: `300`] + be forgotten. [default: `300`] * `--ifup `: @@ -109,7 +109,7 @@ in 3 different modes: * **Switch mode**: In this mode, the VPN will dynamically learn addresses as they are used as source addresses and use them to forward data to its destination. Addresses that have not been seen for some time - (option `dst_timeout`) will be forgot. Data for unknown addresses will be + (option `dst_timeout`) will be forgotten. Data for unknown addresses will be broadcast to all peers. This mode is the default mode for TAP devices that process Ethernet frames but it can also be used with TUN devices and IP packets.