diff --git a/src/cloud.rs b/src/cloud.rs index dc28838..b00b334 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -256,7 +256,7 @@ impl GenericCloud fail!("Failed to open ipv6 address ::{}: {}", config.port, err) }; let now = TS::now(); - let peer_timeout_publish = if S::detect_nat() { + let peer_timeout_publish = if socket4.detect_nat() { info!("Private IP detected, setting published peer timeout to 300s"); 300 } else { diff --git a/src/net.rs b/src/net.rs index 4f8e2d0..b5c7faf 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,11 +1,12 @@ use std::{ - collections::VecDeque, + collections::{HashMap, VecDeque}, io::{self, ErrorKind}, net::{SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket}, - os::unix::io::{AsRawFd, RawFd} + os::unix::io::{AsRawFd, RawFd}, + sync::atomic::{AtomicBool, Ordering} }; -use super::util::get_internal_ip; +use super::util::{get_internal_ip, MockTimeSource, Time, TimeSource}; use net2::UdpBuilder; @@ -16,7 +17,7 @@ pub trait Socket: AsRawFd + Sized { fn receive(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), io::Error>; fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result; fn address(&self) -> Result; - fn detect_nat() -> bool; + fn detect_nat(&self) -> bool; } impl Socket for UdpSocket { @@ -45,13 +46,18 @@ impl Socket for UdpSocket { fn address(&self) -> Result { self.local_addr() } - fn detect_nat() -> bool { + fn detect_nat(&self) -> bool { get_internal_ip().is_private() } } +thread_local! { + static MOCK_SOCKET_NAT: AtomicBool = AtomicBool::new(false); +} pub struct MockSocket { + nat: bool, + nat_peers: HashMap, address: SocketAddr, outbound: VecDeque<(SocketAddr, Vec)>, inbound: VecDeque<(SocketAddr, Vec)> @@ -59,11 +65,36 @@ pub struct MockSocket { impl MockSocket { pub fn new(address: SocketAddr) -> Self { - Self { address, outbound: VecDeque::new(), inbound: VecDeque::new() } + Self { + nat: Self::get_nat(), + nat_peers: HashMap::new(), + address, + outbound: VecDeque::new(), + inbound: VecDeque::new() + } } - pub fn put_inbound(&mut self, from: SocketAddr, data: Vec) { - self.inbound.push_back((from, data)) + pub fn set_nat(nat: bool) { + MOCK_SOCKET_NAT.with(|t| t.store(nat, Ordering::SeqCst)) + } + + pub fn get_nat() -> bool { + MOCK_SOCKET_NAT.with(|t| t.load(Ordering::SeqCst)) + } + + pub fn put_inbound(&mut self, from: SocketAddr, data: Vec) -> bool { + if !self.nat { + self.inbound.push_back((from, data)); + return true + } + if let Some(timeout) = self.nat_peers.get(&from) { + if *timeout >= MockTimeSource::now() { + self.inbound.push_back((from, data)); + return true + } + } + warn!("Sender {:?} is filtered out by NAT", from); + false } pub fn pop_outbound(&mut self) -> Option<(SocketAddr, Vec)> { @@ -91,17 +122,20 @@ impl Socket for MockSocket { buffer[0..data.len()].copy_from_slice(&data); Ok((data.len(), addr)) } else { - Err(io::Error::from(ErrorKind::UnexpectedEof)) + Err(io::Error::new(ErrorKind::Other, "nothing in queue")) } } fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result { self.outbound.push_back((addr, data.to_owned())); + if self.nat { + self.nat_peers.insert(addr, MockTimeSource::now() + 300); + } Ok(data.len()) } fn address(&self) -> Result { Ok(self.address) } - fn detect_nat() -> bool { - false + fn detect_nat(&self) -> bool { + self.nat } } diff --git a/src/tests/helper.rs b/src/tests/helper.rs index 4533dd7..8302df8 100644 --- a/src/tests/helper.rs +++ b/src/tests/helper.rs @@ -39,6 +39,20 @@ macro_rules! simulate { }; } +macro_rules! simulate_time { + ($time:expr, $($node: expr => $addr: expr),*) => { + for _ in 0..$time { + use crate::util::{MockTimeSource, TimeSource}; + MockTimeSource::set_time(MockTimeSource::now()+1); + $( + $node.trigger_housekeep(); + )* + simulate(&mut [$((&mut $node, $addr)),*]); + } + }; +} + + macro_rules! assert_connected { ($($node:expr),*) => { for node1 in [$(&$node),*].iter() { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 42a8f2b..8c768e1 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -4,11 +4,18 @@ #[macro_use] mod helper; +mod nat; mod payload; mod peers; pub use std::net::SocketAddr; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + io::Write, + sync::{ + atomic::{AtomicUsize, Ordering}, + Once + } +}; pub use super::{ cloud::GenericCloud, @@ -24,6 +31,37 @@ pub use super::{ }; +static INIT_LOGGER: Once = Once::new(); + +pub fn init_debug_logger() { + INIT_LOGGER.call_once(|| { + log::set_boxed_logger(Box::new(DebugLogger)).unwrap(); + log::set_max_level(log::LevelFilter::Debug); + }) +} + +struct DebugLogger; + +impl log::Log for DebugLogger { + #[inline] + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + #[inline] + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + eprintln!("{} - {}", record.level(), record.args()); + } + } + + #[inline] + fn flush(&self) { + std::io::stderr().flush().expect("Failed to flush") + } +} + + type TestNode = GenericCloud; type TapTestNode = TestNode>; @@ -35,17 +73,19 @@ thread_local! { static NEXT_PORT: AtomicUsize = AtomicUsize::new(1); } -fn create_tap_node() -> TapTestNode { - create_tap_node_with_config(Config::default()) +fn create_tap_node(nat: bool) -> TapTestNode { + create_tap_node_with_config(nat, Config::default()) } -fn create_tap_node_with_config(mut config: Config) -> TapTestNode { +fn create_tap_node_with_config(nat: bool, mut config: Config) -> TapTestNode { + MockSocket::set_nat(nat); config.port = NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16; TestNode::new(&config, MockDevice::new(), SwitchTable::new(1800, 10), true, true, vec![], Crypto::None, None) } #[allow(dead_code)] -fn create_tun_node(addresses: Vec) -> TunTestNode { +fn create_tun_node(nat: bool, addresses: Vec) -> TunTestNode { + MockSocket::set_nat(nat); TestNode::new( &Config { port: NEXT_PORT.with(|p| p.fetch_add(1, Ordering::Relaxed)) as u16, ..Config::default() }, MockDevice::new(), @@ -73,13 +113,15 @@ fn msg6_get(node: &mut TestNode) -> (SocketAddr, Ve } fn msg4_put(node: &mut TestNode, from: SocketAddr, msg: Vec) { - node.socket4().put_inbound(from, msg); - node.trigger_socket_v4_event(); + if 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(); + if node.socket6().put_inbound(from, msg) { + node.trigger_socket_v6_event(); + } } fn simulate(nodes: &mut [(&mut TestNode, SocketAddr)]) { diff --git a/src/tests/nat.rs b/src/tests/nat.rs new file mode 100644 index 0000000..2387adf --- /dev/null +++ b/src/tests/nat.rs @@ -0,0 +1,86 @@ +// 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_nat_2_peers() { + init_debug_logger(); + MockTimeSource::set_time(0); + let mut node1 = create_tap_node(true); + let node1_addr = addr!("1.2.3.4:5678"); + let mut node2 = create_tap_node(false); + let node2_addr = addr!("2.3.4.5:6789"); + + node2.connect("1.2.3.4:5678").unwrap(); + + simulate!(node1 => node1_addr, node2 => node2_addr); + + assert!(!node1.peers().contains_node(&node2.node_id())); + assert!(!node2.peers().contains_node(&node1.node_id())); + + + node1.connect("2.3.4.5:6789").unwrap(); + + simulate!(node1 => node1_addr, node2 => node2_addr); + + assert_connected!(node1, node2); +} + +#[test] +fn connect_nat_3_peers() { + init_debug_logger(); + MockTimeSource::set_time(0); + let mut node1 = create_tap_node(true); + let node1_addr = addr!("1.2.3.4:5678"); + let mut node2 = create_tap_node(false); + let node2_addr = addr!("2.3.4.5:6789"); + let mut node3 = create_tap_node(false); + let node3_addr = addr!("3.4.5.6:7890"); + node2.connect("1.2.3.4:5678").unwrap(); + node3.connect("1.2.3.4:5678").unwrap(); + simulate!(node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + + assert!(!node1.peers().contains_node(&node2.node_id())); + assert!(!node2.peers().contains_node(&node1.node_id())); + assert!(!node3.peers().contains_node(&node1.node_id())); + assert!(!node3.peers().contains_node(&node2.node_id())); + assert!(!node1.peers().contains_node(&node3.node_id())); + assert!(!node2.peers().contains_node(&node3.node_id())); + + node1.connect("3.4.5.6:7890").unwrap(); + node2.connect("3.4.5.6:7890").unwrap(); + + simulate_time!(1000, node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + + assert_connected!(node1, node3); + assert_connected!(node2, node3); + assert_connected!(node1, node2); +} + +#[test] +fn nat_keepalive() { + init_debug_logger(); + MockTimeSource::set_time(0); + let mut node1 = create_tap_node(true); + let node1_addr = addr!("1.2.3.4:5678"); + let mut node2 = create_tap_node(false); + let node2_addr = addr!("2.3.4.5:6789"); + let mut node3 = create_tap_node(false); + let node3_addr = addr!("3.4.5.6:7890"); + node1.connect("3.4.5.6:7890").unwrap(); + node2.connect("3.4.5.6:7890").unwrap(); + + simulate_time!(1000, node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + + assert_connected!(node1, node3); + assert_connected!(node2, node3); + assert_connected!(node1, node2); + + simulate_time!(10000, node1 => node1_addr, node2 => node2_addr, node3 => node3_addr); + + assert_connected!(node1, node3); + assert_connected!(node2, node3); + assert_connected!(node1, node2); +} diff --git a/src/tests/payload.rs b/src/tests/payload.rs index f8a30f7..715df9d 100644 --- a/src/tests/payload.rs +++ b/src/tests/payload.rs @@ -6,9 +6,9 @@ use super::*; #[test] fn ethernet_delivers() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.2.3.4:5678"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("2.3.4.5:6789"); node1.connect("2.3.4.5:6789").unwrap(); @@ -28,11 +28,11 @@ fn ethernet_delivers() { #[test] fn switch_learns() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.2.3.4:5678"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("2.3.4.5:6789"); - let mut node3 = create_tap_node(); + let mut node3 = create_tap_node(false); let node3_addr = addr!("3.4.5.6:7890"); node1.connect("2.3.4.5:6789").unwrap(); diff --git a/src/tests/peers.rs b/src/tests/peers.rs index b251035..3bfc2a9 100644 --- a/src/tests/peers.rs +++ b/src/tests/peers.rs @@ -6,9 +6,9 @@ use super::*; #[test] fn connect_v4() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.2.3.4:5678"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("2.3.4.5:6789"); assert_clean!(node1, node2); assert!(!node1.peers().contains_node(&node2.node_id())); @@ -43,9 +43,9 @@ fn connect_v4() { #[test] fn connect_v6() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("[::1]:5678"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("[::2]:6789"); node1.connect("[::2]:6789").unwrap(); @@ -57,13 +57,13 @@ fn connect_v6() { #[test] fn cross_connect() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.1.1.1:1111"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("2.2.2.2:2222"); - let mut node3 = create_tap_node(); + let mut node3 = create_tap_node(false); let node3_addr = addr!("3.3.3.3:3333"); - let mut node4 = create_tap_node(); + let mut node4 = create_tap_node(false); let node4_addr = addr!("4.4.4.4:4444"); node1.connect("2.2.2.2:2222").unwrap(); @@ -98,10 +98,10 @@ fn connect_via_beacons() { MockTimeSource::set_time(0); let beacon_path = "target/.vpncloud_test"; let mut node1 = - create_tap_node_with_config(Config { beacon_store: Some(beacon_path.to_string()), ..Config::default() }); + create_tap_node_with_config(false, Config { beacon_store: Some(beacon_path.to_string()), ..Config::default() }); let node1_addr = node1.address().unwrap().0; let mut node2 = - create_tap_node_with_config(Config { beacon_load: Some(beacon_path.to_string()), ..Config::default() }); + create_tap_node_with_config(false, Config { beacon_load: Some(beacon_path.to_string()), ..Config::default() }); let node2_addr = addr!("2.2.2.2:2222"); assert!(!node1.peers().contains_node(&node2.node_id())); @@ -122,9 +122,9 @@ fn connect_via_beacons() { #[test] fn reconnect_after_timeout() { MockTimeSource::set_time(0); - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.1.1.1:1111"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("2.2.2.2:2222"); node1.add_reconnect_peer("2.2.2.2:2222".to_string()); @@ -148,9 +148,9 @@ fn reconnect_after_timeout() { #[test] fn lost_init1() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.2.3.4:5678"); - let mut node2 = create_tap_node(); + let mut node2 = create_tap_node(false); let node2_addr = addr!("2.3.4.5:6789"); node1.connect("2.3.4.5:6789").unwrap(); @@ -170,10 +170,10 @@ fn lost_init1() { #[test] fn wrong_magic() { - let mut node1 = create_tap_node(); + let mut node1 = create_tap_node(false); let node1_addr = addr!("1.2.3.4:5678"); let mut node2 = - create_tap_node_with_config(Config { magic: Some("hash:different".to_string()), ..Config::default() }); + create_tap_node_with_config(false, Config { magic: Some("hash:different".to_string()), ..Config::default() }); let node2_addr = addr!("2.3.4.5:6789"); node1.connect("2.3.4.5:6789").unwrap(); diff --git a/src/util.rs b/src/util.rs index d32ec3d..5171870 100644 --- a/src/util.rs +++ b/src/util.rs @@ -91,11 +91,13 @@ macro_rules! fail { ($format:expr) => ( { use std::process; error!($format); + log::logger().flush(); process::exit(-1); } ); ($format:expr, $( $arg:expr ),+) => ( { use std::process; error!($format, $( $arg ),+ ); + log::logger().flush(); process::exit(-1); } ); }