diff --git a/src/cloud.rs b/src/cloud.rs index 6a2bf12..f80f1ec 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -40,7 +40,7 @@ struct PeerData { alt_addrs: Vec, } -struct PeerList { +pub struct PeerList { timeout: Duration, peers: HashMap, nodes: HashMap, @@ -81,12 +81,12 @@ impl PeerList { } #[inline] - fn contains_addr(&self, addr: &SocketAddr) -> bool { + pub fn contains_addr(&self, addr: &SocketAddr) -> bool { self.addresses.contains_key(addr) } #[inline] - fn is_connected(&self, addr: Addr) -> Result { + pub fn is_connected(&self, addr: Addr) -> Result { for addr in try!(resolve(&addr)) { if self.contains_addr(&addr) { return Ok(true); @@ -96,7 +96,7 @@ impl PeerList { } #[inline] - fn contains_node(&self, node_id: &NodeId) -> bool { + pub fn contains_node(&self, node_id: &NodeId) -> bool { self.nodes.contains_key(node_id) } @@ -142,23 +142,23 @@ impl PeerList { } #[inline] - fn get_node_id(&self, addr: &SocketAddr) -> Option { + pub fn get_node_id(&self, addr: &SocketAddr) -> Option { self.addresses.get(addr).map(|n| *n) } #[inline] - fn as_vec(&self) -> Vec { + pub fn as_vec(&self) -> Vec { self.addresses.keys().cloned().collect() } #[inline] - fn len(&self) -> usize { + pub fn len(&self) -> usize { self.peers.len() } #[inline] #[allow(dead_code)] - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.peers.is_empty() } @@ -687,12 +687,13 @@ impl GenericCloud { self.peers.remove(&peer); @@ -712,10 +713,6 @@ impl GenericCloud(&self, msg: &'a mut [u8]) -> Result, Error> { - decode(msg, self.magic, &self.crypto) - } - fn handle_socket_data(&mut self, src: SocketAddr, data: &mut [u8]) { let size = data.len(); if let Err(e) = decode(data, self.magic, &mut self.crypto).and_then(|msg| { @@ -788,42 +785,52 @@ impl GenericCloud GenericCloud { - fn is_empty(&self) -> bool { - self.device.is_empty() && self.socket4.is_empty() && self.socket6.is_empty() +impl GenericCloud { + pub fn socket4(&mut self) -> &mut MockSocket { + &mut self.socket4 + } + + pub fn socket6(&mut self) -> &mut MockSocket { + &mut self.socket6 + } + + pub fn device(&mut self) -> &mut MockDevice { + &mut self.device + } + + pub fn trigger_socket_v4_event(&mut self) { + let mut buffer = [0; 64*1024]; + self.handle_socket_v4_event(&mut buffer); + } + + pub fn trigger_socket_v6_event(&mut self) { + let mut buffer = [0; 64*1024]; + self.handle_socket_v6_event(&mut buffer); + } + + pub fn trigger_device_event(&mut self) { + let mut buffer = [0; 64*1024]; + self.handle_device_event(&mut buffer); + } + + pub fn node_id(&self) -> NodeId { + self.node_id + } + + pub fn peers(&self) -> &PeerList { + &self.peers + } + + pub fn own_addresses(&self) -> &[SocketAddr] { + &self.own_addresses + } + + pub fn decode_message<'a>(&self, msg: &'a mut [u8]) -> Result, Error> { + decode(msg, self.magic, &self.crypto) } } - -#[cfg(test)] -type TestNode = GenericCloud, MockSocket, MockTimeSource>; - -#[cfg(test)] -fn create_node() -> TestNode { - TestNode::new( - &Config::default(), - MockDevice::new(), - SwitchTable::new(1800, 10), - true, true, vec![], Crypto::None, None - ) -} - -#[test] -fn connect() { - let mut node = create_node(); - assert!(node.is_empty()); - node.connect("1.2.3.4:5678").unwrap(); - assert!(node.device.is_empty()); - assert!(node.socket6.is_empty()); - let (addr, mut message) = node.socket4.pop_outbound().unwrap(); - assert_eq!("1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap(), addr); - let message = node.decode_message(&mut message).unwrap(); - assert_eq!(Message::Init(0, node.node_id, vec![]), message); - -} \ No newline at end of file diff --git a/src/device.rs b/src/device.rs index 4537e94..87b6ff8 100644 --- a/src/device.rs +++ b/src/device.rs @@ -259,8 +259,8 @@ impl MockDevice { self.outbound.pop_front() } - pub fn is_empty(&self) -> bool { - self.inbound.is_empty() && self.outbound.is_empty() + pub fn has_inbound(&self) -> bool { + !self.inbound.is_empty() } } diff --git a/src/main.rs b/src/main.rs index fd81265..41755ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ extern crate base_62; #[cfg(feature = "bench")] extern crate test; #[macro_use] pub mod util; +#[cfg(test)] #[macro_use] mod tests; pub mod types; pub mod crypto; pub mod udpmessage; diff --git a/src/net.rs b/src/net.rs index 936a9e3..4fd2d34 100644 --- a/src/net.rs +++ b/src/net.rs @@ -54,10 +54,6 @@ impl MockSocket { pub fn pop_outbound(&mut self) -> Option<(SocketAddr, Vec)> { self.outbound.pop_front() } - - pub fn is_empty(&self) -> bool { - self.inbound.is_empty() && self.outbound.is_empty() - } } impl AsRawFd for MockSocket { diff --git a/src/tests/connect.rs b/src/tests/connect.rs new file mode 100644 index 0000000..49f3b48 --- /dev/null +++ b/src/tests/connect.rs @@ -0,0 +1,94 @@ +// VpnCloud - Peer-to-Peer VPN +// Copyright (C) 2015-2019 Dennis Schwerdel +// This software is licensed under GPL-3 or newer (see LICENSE.md) + +use super::*; + +#[test] +fn connect_v4() { + let mut node1 = create_tap_node(); + let node1_addr = addr!("1.2.3.4:5678"); + let mut node2 = create_tap_node(); + let node2_addr = addr!("2.3.4.5:6789"); + assert_clean!(node1, node2); + assert!(!node1.peers().contains_node(&node2.node_id())); + assert!(!node2.peers().contains_node(&node1.node_id())); + + node1.connect("2.3.4.5:6789").unwrap(); + + // Node 1 -> Node 2: Init 0 + assert_message4!(node1, node1_addr, node2, node2_addr, Message::Init(0, node1.node_id(), vec![])); + assert_clean!(node1); + assert!(node2.peers().contains_node(&node1.node_id())); + + // Node 2 -> Node 1: Init 1 | Node 2 -> Node 1: Peers + assert_message4!(node2, node2_addr, node1, node1_addr, Message::Init(1, node2.node_id(), vec![])); + assert!(node1.peers().contains_node(&node2.node_id())); + assert_message4!(node2, node2_addr, node1, node1_addr, Message::Peers(vec![node1_addr])); + assert_clean!(node2); + + // Node 1 -> Node 2: Peers | Node 1 -> Node 1: Init 0 + assert_message4!(node1, node1_addr, node2, node2_addr, Message::Peers(vec![node2_addr])); + assert_message4!(node1, node1_addr, node1, node1_addr, Message::Init(0, node1.node_id(), vec![])); + assert!(node1.own_addresses().contains(&node1_addr)); + assert_clean!(node1); + + // Node 2 -> Node 2: Init 0 + assert_message4!(node2, node2_addr, node2, node2_addr, Message::Init(0, node2.node_id(), vec![])); + assert_clean!(node2); + assert!(node2.own_addresses().contains(&node2_addr)); + + assert_connected!(node1, node2); +} + +#[test] +fn connect_v6() { + let mut node1 = create_tap_node(); + let node1_addr = addr!("[::1]:5678"); + let mut node2 = create_tap_node(); + let node2_addr = addr!("[::2]:6789"); + + node1.connect("[::2]:6789").unwrap(); + + simulate!(node1 => node1_addr, node2 => node2_addr); + + assert_connected!(node1, node2); +} + +#[test] +fn cross_connect() { + let mut node1 = create_tap_node(); + let node1_addr = addr!("1.1.1.1:1111"); + let mut node2 = create_tap_node(); + let node2_addr = addr!("2.2.2.2:2222"); + let mut node3 = create_tap_node(); + let node3_addr = addr!("3.3.3.3:3333"); + let mut node4 = create_tap_node(); + let node4_addr = addr!("4.4.4.4:4444"); + + node1.connect("2.2.2.2:2222").unwrap(); + node3.connect("4.4.4.4:4444").unwrap(); + + simulate!(node1 => node1_addr, node2 => node2_addr, node3 => node3_addr, node4 => node4_addr); + + assert_connected!(node1, node2); + assert_connected!(node3, node4); + + node1.connect("3.3.3.3:3333").unwrap(); + + simulate!(node1 => node1_addr, node2 => node2_addr, node3 => node3_addr, node4 => node4_addr); + + // existing connections + assert_connected!(node1, node2); + assert_connected!(node3, node4); + + // new connection + assert_connected!(node1, node3); + + // transient connections 1st degree + assert_connected!(node1, node4); + assert_connected!(node3, node2); + + // transient connections 2nd degree + assert_connected!(node1, node4); +} diff --git a/src/tests/helper.rs b/src/tests/helper.rs new file mode 100644 index 0000000..019b5fa --- /dev/null +++ b/src/tests/helper.rs @@ -0,0 +1,52 @@ +macro_rules! assert_clean { + ($($node: expr),*) => { + $( + assert_eq!($node.socket4().pop_outbound().map(|(addr, mut msg)| (addr, $node.decode_message(&mut msg).unwrap().without_data())), None); + assert_eq!($node.socket6().pop_outbound().map(|(addr, mut msg)| (addr, $node.decode_message(&mut msg).unwrap().without_data())), None); + assert_eq!($node.device().pop_outbound(), None); + )* + }; +} + +macro_rules! assert_message4 { + ($from: expr, $from_addr: expr, $to: expr, $to_addr: expr, $message: expr) => { + let (addr, mut data) = msg4_get(&mut $from); + assert_eq!($to_addr, addr); + { + let message = $to.decode_message(&mut data).unwrap(); + assert_eq!($message, message.without_data()); + } + msg4_put(&mut $to, $from_addr, data); + }; +} + +macro_rules! assert_message6 { + ($from: expr, $from_addr: expr, $to: expr, $to_addr: expr, $message: expr) => { + let (addr, mut data) = msg6_get(&mut $from); + assert_eq!($to_addr, addr); + { + let message = $to.decode_message(&mut data).unwrap(); + assert_eq!($message, message.without_data()); + } + msg6_put(&mut $to, $from_addr, data); + }; +} + +macro_rules! simulate { + ($($node: expr => $addr: expr),*) => { + simulate(&mut [$((&mut $node, $addr)),*]); + }; +} + +macro_rules! assert_connected { + ($($node:expr),*) => { + for node1 in [$(&$node),*].iter() { + for node2 in [$(&$node),*].iter() { + if node1.node_id() == node2.node_id() { + continue + } + assert!(node1.peers().contains_node(&node2.node_id())); + } + } + }; +} \ No newline at end of file diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..2c1b01c --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,110 @@ +// VpnCloud - Peer-to-Peer VPN +// Copyright (C) 2015-2019 Dennis Schwerdel +// This software is licensed under GPL-3 or newer (see LICENSE.md) + +#[macro_use] mod helper; +mod connect; +mod payload; + +pub use std::net::SocketAddr; + +pub use super::ethernet::{self, SwitchTable}; +pub use super::util::MockTimeSource; +pub use super::net::MockSocket; +pub use super::device::MockDevice; +pub use super::udpmessage::Message; +pub use super::config::Config; +pub use super::crypto::Crypto; +pub use super::cloud::GenericCloud; +pub use super::types::{Protocol, Table, Range}; +pub use super::ip::{self, RoutingTable}; + + +type TestNode = GenericCloud; + +type TapTestNode = TestNode>; +type TunTestNode = TestNode; + + +fn create_tap_node() -> TapTestNode { + TestNode::new( + &Config::default(), + MockDevice::new(), + SwitchTable::new(1800, 10), + true, true, vec![], Crypto::None, None + ) +} + +fn create_tun_node(addresses: Vec) -> TunTestNode { + TestNode::new( + &Config::default(), + MockDevice::new(), + RoutingTable::new(), + false, false, addresses, Crypto::None, None + ) +} + + +fn msg4_get(node: &mut TestNode) -> (SocketAddr, Vec) { + let msg = node.socket4().pop_outbound(); + assert!(msg.is_some()); + msg.unwrap() +} + +fn msg6_get(node: &mut TestNode) -> (SocketAddr, Vec) { + let msg = node.socket6().pop_outbound(); + assert!(msg.is_some()); + msg.unwrap() +} + +fn msg4_put(node: &mut TestNode, from: SocketAddr, msg: Vec) { + node.socket4().put_inbound(from, msg); + node.trigger_socket_v4_event(); +} + +fn msg6_put(node: &mut TestNode, from: SocketAddr, msg: Vec) { + node.socket6().put_inbound(from, msg); + node.trigger_socket_v6_event(); +} + +fn simulate(nodes: &mut [(&mut TestNode, SocketAddr)]) { + for (ref mut node, ref from_addr) in nodes.iter_mut() { + while node.device().has_inbound() { + node.trigger_device_event(); + } + } + let mut clean = false; + while !clean { + clean = true; + let mut msgs = Vec::new(); + for (ref mut node, ref from_addr) in nodes.iter_mut() { + while let Some((to_addr, msg)) = node.socket4().pop_outbound() { + msgs.push((msg, *from_addr, to_addr)); + } + } + clean &= msgs.is_empty(); + for (msg, from_addr, to_addr) in msgs { + for (ref mut node, ref addr) in nodes.iter_mut() { + if *addr == to_addr { + msg4_put(node, from_addr, msg); + break + } + } + } + let mut msgs = Vec::new(); + for (ref mut node, ref from_addr) in nodes.iter_mut() { + while let Some((to_addr, msg)) = node.socket6().pop_outbound() { + msgs.push((msg, *from_addr, to_addr)); + } + } + clean &= msgs.is_empty(); + for (msg, from_addr, to_addr) in msgs { + for (ref mut node, ref addr) in nodes.iter_mut() { + if *addr == to_addr { + msg6_put(node, from_addr, msg); + break + } + } + } + } +} \ No newline at end of file diff --git a/src/tests/payload.rs b/src/tests/payload.rs new file mode 100644 index 0000000..41bda24 --- /dev/null +++ b/src/tests/payload.rs @@ -0,0 +1,62 @@ +// VpnCloud - Peer-to-Peer VPN +// Copyright (C) 2015-2019 Dennis Schwerdel +// This software is licensed under GPL-3 or newer (see LICENSE.md) + +use super::*; + +#[test] +fn ethernet_send() { + let mut node1 = create_tap_node(); + let node1_addr = addr!("1.2.3.4:5678"); + let mut node2 = create_tap_node(); + let node2_addr = addr!("2.3.4.5:6789"); + + node1.connect("2.3.4.5:6789").unwrap(); + simulate!(node1 => node1_addr, node2 => node2_addr); + assert_connected!(node1, node2); + + let payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]; + + node1.device().put_inbound(payload.clone()); + + simulate!(node1 => node1_addr, node2 => node2_addr); + + assert_eq!(Some(payload), node2.device().pop_outbound()); + + assert_clean!(node1, node2); +} + +#[test] +fn learning_switch() { + let mut node1 = create_tap_node(); + let node1_addr = addr!("1.2.3.4:5678"); + let mut node2 = create_tap_node(); + let node2_addr = addr!("2.3.4.5:6789"); + let mut node3 = create_tap_node(); + let node3_addr = addr!("3.4.5.6:7890"); + + node1.connect("2.3.4.5:6789").unwrap(); + node1.connect("3.4.5.6:7890").unwrap(); + simulate!(node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + assert_connected!(node1, node2, node3); + + let payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]; + + node1.device().put_inbound(payload.clone()); + + simulate!(node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + + assert_eq!(Some(&payload), node2.device().pop_outbound().as_ref()); + assert_eq!(Some(&payload), node3.device().pop_outbound().as_ref()); + + let payload = vec![1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1]; + + node2.device().put_inbound(payload.clone()); + + simulate!(node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + + assert_eq!(Some(&payload), node1.device().pop_outbound().as_ref()); + assert_clean!(node3); + + assert_clean!(node1, node2, node3); +} \ No newline at end of file diff --git a/src/udpmessage.rs b/src/udpmessage.rs index d154dce..f6f9b71 100644 --- a/src/udpmessage.rs +++ b/src/udpmessage.rs @@ -55,6 +55,17 @@ pub enum Message<'a> { Close, } +impl<'a> Message<'a> { + pub fn without_data(self) -> Message<'static> { + match self { + Message::Data(_, start, end) => Message::Data(&mut [], start, end), + Message::Peers(peers) => Message::Peers(peers), + Message::Init(step, node_id, ranges) => Message::Init(step, node_id, ranges), + Message::Close => Message::Close + } + } +} + impl<'a> fmt::Debug for Message<'a> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { diff --git a/src/util.rs b/src/util.rs index 553f7f3..1d9face 100644 --- a/src/util.rs +++ b/src/util.rs @@ -129,6 +129,14 @@ pub fn resolve(addr: Addr) -> Result { + { + std::net::ToSocketAddrs::to_socket_addrs($addr).unwrap().next().unwrap() + } + }; +} + pub struct Bytes(pub u64);