Tests for NAT

pull/46/head
Dennis Schwerdel 2019-12-04 13:09:20 +01:00
parent 9cd7e53880
commit 63dc1e3ce1
8 changed files with 220 additions and 42 deletions

View File

@ -256,7 +256,7 @@ impl<D: Device, P: Protocol, T: Table, S: Socket, TS: TimeSource> GenericCloud<D
Err(err) => 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 {

View File

@ -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<usize, io::Error>;
fn address(&self) -> Result<SocketAddr, io::Error>;
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<SocketAddr, io::Error> {
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<SocketAddr, Time>,
address: SocketAddr,
outbound: VecDeque<(SocketAddr, Vec<u8>)>,
inbound: VecDeque<(SocketAddr, Vec<u8>)>
@ -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<u8>) {
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<u8>) -> 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<u8>)> {
@ -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<usize, io::Error> {
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<SocketAddr, io::Error> {
Ok(self.address)
}
fn detect_nat() -> bool {
false
fn detect_nat(&self) -> bool {
self.nat
}
}

View File

@ -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() {

View File

@ -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<P, T> = GenericCloud<MockDevice, P, T, MockSocket, MockTimeSource>;
type TapTestNode = TestNode<ethernet::Frame, SwitchTable<MockTimeSource>>;
@ -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<Range>) -> TunTestNode {
fn create_tun_node(nat: bool, addresses: Vec<Range>) -> 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<P: Protocol, T: Table>(node: &mut TestNode<P, T>) -> (SocketAddr, Ve
}
fn msg4_put<P: Protocol, T: Table>(node: &mut TestNode<P, T>, from: SocketAddr, msg: Vec<u8>) {
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<P: Protocol, T: Table>(node: &mut TestNode<P, T>, from: SocketAddr, msg: Vec<u8>) {
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<P: Protocol, T: Table>(nodes: &mut [(&mut TestNode<P, T>, SocketAddr)]) {

86
src/tests/nat.rs Normal file
View File

@ -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);
}

View File

@ -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();

View File

@ -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();

View File

@ -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);
} );
}