From 2955a80af422d0f6902c84c5a785bc40e4289e0f Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Mon, 8 Feb 2021 17:30:58 +0100 Subject: [PATCH] More benchmarking --- benches/.code.rs | 77 +++++++ benches/criterion.rs | 95 ++++++-- benches/valgrind.rs | 85 ++++++-- src/crypto/common.rs | 498 ++++++++++++++++++++++++++++++++++++++++++ src/crypto/core.rs | 2 +- src/crypto/init.rs | 7 +- src/crypto/mod.rs | 503 +------------------------------------------ src/crypto/rotate.rs | 3 +- src/device.rs | 4 +- src/net.rs | 6 +- src/tests/common.rs | 214 ++++++++++++++++++ src/tests/mod.rs | 214 +----------------- src/tests/nat.rs | 2 +- src/tests/payload.rs | 2 +- src/tests/peers.rs | 2 +- 15 files changed, 948 insertions(+), 766 deletions(-) create mode 100644 benches/.code.rs create mode 100644 src/crypto/common.rs create mode 100644 src/tests/common.rs diff --git a/benches/.code.rs b/benches/.code.rs new file mode 100644 index 0000000..ad14927 --- /dev/null +++ b/benches/.code.rs @@ -0,0 +1,77 @@ +#[macro_use] +mod util { + include!("../src/util.rs"); +} +mod error { + include!("../src/error.rs"); +} +mod payload { + include!("../src/payload.rs"); +} +mod types { + include!("../src/types.rs"); +} +mod table { + include!("../src/table.rs"); +} +mod cloud { + include!("../src/cloud.rs"); +} +mod config { + include!("../src/config.rs"); +} +mod device { + include!("../src/device.rs"); +} +mod net { + include!("../src/net.rs"); +} +mod beacon { + include!("../src/beacon.rs"); +} +mod messages { + include!("../src/messages.rs"); +} +mod port_forwarding { + include!("../src/port_forwarding.rs"); +} +mod traffic { + include!("../src/traffic.rs"); +} +mod poll { + pub mod epoll{ + include!("../src/poll/epoll.rs"); + } + #[cfg(any(target_os = "linux", target_os = "android"))] + pub use self::epoll::EpollWait as WaitImpl; + + use std::io; + + pub enum WaitResult { + Timeout, + Socket, + Device, + Error(io::Error) + } +} +mod crypto { + pub mod core { + include!("../src/crypto/core.rs"); + } + pub mod init { + include!("../src/crypto/init.rs"); + } + pub mod rotate { + include!("../src/crypto/rotate.rs"); + } + pub mod common { + include!("../src/crypto/common.rs"); + } + pub use common::*; + pub use self::core::{EXTRA_LEN, TAG_LEN}; +} +mod tests { + pub mod common { + include!("../src/tests/common.rs"); + } +} diff --git a/benches/criterion.rs b/benches/criterion.rs index 5c2b676..6d4f6ad 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -10,31 +10,17 @@ use ring::aead; use std::str::FromStr; use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket}; -mod util { - include!("../src/util.rs"); -} -mod error { - include!("../src/error.rs"); -} -mod payload { - include!("../src/payload.rs"); -} -mod types { - include!("../src/types.rs"); -} -mod table { - include!("../src/table.rs"); -} -mod crypto_core { - include!("../src/crypto/core.rs"); -} +include!(".code.rs"); pub use error::Error; use util::{MockTimeSource, MsgBuffer}; use types::{Address, Range}; use table::{ClaimTable}; +use device::Type; +use config::Config; use payload::{Packet, Frame, Protocol}; -use crypto_core::{create_dummy_pair, EXTRA_LEN}; +use crypto::core::{create_dummy_pair, EXTRA_LEN}; +use tests::common::{TunSimulator, TapSimulator}; fn udp_send(c: &mut Criterion) { let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); @@ -145,5 +131,74 @@ fn crypto_aes256(c: &mut Criterion) { crypto_bench(c, &aead::AES_256_GCM) } -criterion_group!(benches, udp_send, decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, lookup_cold, lookup_warm, crypto_chacha20, crypto_aes128, crypto_aes256); +fn full_communication_tun_router(c: &mut Criterion) { + log::set_max_level(log::LevelFilter::Error); + let config1 = Config { + device_type: Type::Tun, + auto_claim: false, + claims: vec!["1.1.1.1/32".to_string()], + ..Config::default() + }; + let config2 = Config { + device_type: Type::Tun, + auto_claim: false, + claims: vec!["2.2.2.2/32".to_string()], + ..Config::default() + }; + let mut sim = TunSimulator::new(); + let node1 = sim.add_node(false, &config1); + let node2 = sim.add_node(false, &config2); + + sim.connect(node1, node2); + sim.simulate_all_messages(); + assert!(sim.is_connected(node1, node2)); + assert!(sim.is_connected(node2, node1)); + + let mut payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + payload.append(&mut vec![0; 1400]); + let mut g = c.benchmark_group("full_communication"); + g.throughput(Throughput::Bytes(2*1400)); + g.bench_function("tun_router", |b| { + b.iter(|| { + sim.put_payload(node1, payload.clone()); + sim.simulate_all_messages(); + assert_eq!(Some(&payload), sim.pop_payload(node2).as_ref()); + }); + }); + g.finish() +} + +fn full_communication_tap_switch(c: &mut Criterion) { + log::set_max_level(log::LevelFilter::Error); + let config = Config { device_type: Type::Tap, ..Config::default() }; + let mut sim = TapSimulator::new(); + let node1 = sim.add_node(false, &config); + let node2 = sim.add_node(false, &config); + + sim.connect(node1, node2); + sim.simulate_all_messages(); + assert!(sim.is_connected(node1, node2)); + assert!(sim.is_connected(node2, node1)); + + let mut payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]; + payload.append(&mut vec![0; 1400]); + let mut g = c.benchmark_group("full_communication"); + g.throughput(Throughput::Bytes(2*1400)); + g.bench_function("tap_switch", |b| { + b.iter(|| { + sim.put_payload(node1, payload.clone()); + sim.simulate_all_messages(); + assert_eq!(Some(&payload), sim.pop_payload(node2).as_ref()); + }); + }); + g.finish() +} + +criterion_group!(benches, + udp_send, + decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, + lookup_cold, lookup_warm, + crypto_chacha20, crypto_aes128, crypto_aes256, + full_communication_tun_router, full_communication_tap_switch +); criterion_main!(benches); \ No newline at end of file diff --git a/benches/valgrind.rs b/benches/valgrind.rs index 47fc837..741be70 100644 --- a/benches/valgrind.rs +++ b/benches/valgrind.rs @@ -10,31 +10,17 @@ use ring::aead; use std::str::FromStr; use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket}; -mod util { - include!("../src/util.rs"); -} -mod error { - include!("../src/error.rs"); -} -mod payload { - include!("../src/payload.rs"); -} -mod types { - include!("../src/types.rs"); -} -mod table { - include!("../src/table.rs"); -} -mod crypto_core { - include!("../src/crypto/core.rs"); -} +include!(".code.rs"); pub use error::Error; use util::{MockTimeSource, MsgBuffer}; +use config::Config; use types::{Address, Range}; +use device::Type; use table::{ClaimTable}; use payload::{Packet, Frame, Protocol}; -use crypto_core::{create_dummy_pair, EXTRA_LEN}; +use crypto::core::{create_dummy_pair, EXTRA_LEN}; +use tests::common::{TunSimulator, TapSimulator}; fn udp_send() { let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); @@ -107,4 +93,63 @@ fn crypto_aes256() { crypto_bench(&aead::AES_256_GCM) } -iai::main!(udp_send, decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, lookup_cold, lookup_warm, crypto_chacha20, crypto_aes128, crypto_aes256); \ No newline at end of file +fn full_communication_tun_router() { + log::set_max_level(log::LevelFilter::Error); + let config1 = Config { + device_type: Type::Tun, + auto_claim: false, + claims: vec!["1.1.1.1/32".to_string()], + ..Config::default() + }; + let config2 = Config { + device_type: Type::Tun, + auto_claim: false, + claims: vec!["2.2.2.2/32".to_string()], + ..Config::default() + }; + let mut sim = TunSimulator::new(); + let node1 = sim.add_node(false, &config1); + let node2 = sim.add_node(false, &config2); + + sim.connect(node1, node2); + sim.simulate_all_messages(); + assert!(sim.is_connected(node1, node2)); + assert!(sim.is_connected(node2, node1)); + + let mut payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; + payload.append(&mut vec![0; 1400]); + for _ in 0..1000 { + sim.put_payload(node1, payload.clone()); + sim.simulate_all_messages(); + assert_eq!(Some(&payload), black_box(sim.pop_payload(node2).as_ref())); + } +} + +fn full_communication_tap_switch() { + log::set_max_level(log::LevelFilter::Error); + let config = Config { device_type: Type::Tap, ..Config::default() }; + let mut sim = TapSimulator::new(); + let node1 = sim.add_node(false, &config); + let node2 = sim.add_node(false, &config); + + sim.connect(node1, node2); + sim.simulate_all_messages(); + assert!(sim.is_connected(node1, node2)); + assert!(sim.is_connected(node2, node1)); + + let mut payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]; + payload.append(&mut vec![0; 1400]); + for _ in 0..1000 { + sim.put_payload(node1, payload.clone()); + sim.simulate_all_messages(); + assert_eq!(Some(&payload), black_box(sim.pop_payload(node2).as_ref())); + } +} + +iai::main!( + udp_send, + decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, + lookup_cold, lookup_warm, + crypto_chacha20, crypto_aes128, crypto_aes256, + full_communication_tun_router, full_communication_tap_switch +); \ No newline at end of file diff --git a/src/crypto/common.rs b/src/crypto/common.rs new file mode 100644 index 0000000..db31179 --- /dev/null +++ b/src/crypto/common.rs @@ -0,0 +1,498 @@ +use super::{ + core::{test_speed, CryptoCore}, + init::{self, InitResult, InitState, CLOSING}, + rotate::RotationState +}; +use crate::{ + error::Error, + types::NodeId, + util::{from_base62, to_base62, MsgBuffer} +}; +use ring::{ + aead::{self, Algorithm, LessSafeKey, UnboundKey}, + agreement::{EphemeralPrivateKey, UnparsedPublicKey}, + pbkdf2, + rand::{SecureRandom, SystemRandom}, + signature::{Ed25519KeyPair, KeyPair, ED25519_PUBLIC_KEY_LEN} +}; +use smallvec::{smallvec, SmallVec}; +use std::{fmt::Debug, io::Read, num::NonZeroU32, sync::Arc, time::Duration}; + + +const SALT: &[u8; 32] = b"vpncloudVPNCLOUDvpncl0udVpnCloud"; +const INIT_MESSAGE_FIRST_BYTE: u8 = 0xff; +const MESSAGE_TYPE_ROTATION: u8 = 0x10; + +pub type Ed25519PublicKey = [u8; ED25519_PUBLIC_KEY_LEN]; +pub type EcdhPublicKey = UnparsedPublicKey>; +pub type EcdhPrivateKey = EphemeralPrivateKey; +pub type Key = SmallVec<[u8; 32]>; + + +const DEFAULT_ALGORITHMS: [&str; 3] = ["AES128", "AES256", "CHACHA20"]; + +#[cfg(test)] +const SPEED_TEST_TIME: f32 = 0.02; +#[cfg(not(test))] +const SPEED_TEST_TIME: f32 = 0.1; + +const ROTATE_INTERVAL: usize = 120; + + +pub trait Payload: Debug + PartialEq + Sized { + fn write_to(&self, buffer: &mut MsgBuffer); + fn read_from(r: R) -> Result; +} + + +#[derive(Clone)] +pub struct Algorithms { + pub algorithm_speeds: SmallVec<[(&'static Algorithm, f32); 3]>, + pub allow_unencrypted: bool +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] +#[serde(rename_all = "kebab-case", deny_unknown_fields, default)] +pub struct Config { + pub password: Option, + pub private_key: Option, + pub public_key: Option, + pub trusted_keys: Vec, + pub algorithms: Vec +} + +pub struct Crypto { + node_id: NodeId, + key_pair: Arc, + trusted_keys: Arc<[Ed25519PublicKey]>, + algorithms: Algorithms +} + +impl Crypto { + pub fn new(node_id: NodeId, config: &Config) -> Result { + let key_pair = if let Some(priv_key) = &config.private_key { + if let Some(pub_key) = &config.public_key { + Self::parse_keypair(priv_key, pub_key)? + } else { + Self::parse_private_key(priv_key)? + } + } else if let Some(password) = &config.password { + Self::keypair_from_password(password) + } else { + return Err(Error::InvalidConfig("Either private_key or password must be set")) + }; + let mut trusted_keys = vec![]; + for tn in &config.trusted_keys { + trusted_keys.push(Self::parse_public_key(tn)?); + } + if trusted_keys.is_empty() { + info!("Trusted keys not set, trusting only own public key"); + let mut key = [0; ED25519_PUBLIC_KEY_LEN]; + key.clone_from_slice(key_pair.public_key().as_ref()); + trusted_keys.push(key); + } + let mut algos = Algorithms { algorithm_speeds: smallvec![], allow_unencrypted: false }; + let algorithms = config.algorithms.iter().map(|a| a as &str).collect::>(); + let allowed = if algorithms.is_empty() { &DEFAULT_ALGORITHMS } else { &algorithms as &[&str] }; + let duration = Duration::from_secs_f32(SPEED_TEST_TIME); + let mut speeds = Vec::new(); + for name in allowed { + let algo = match &name.to_uppercase() as &str { + "UNENCRYPTED" | "NONE" | "PLAIN" => { + algos.allow_unencrypted = true; + warn!("Crypto settings allow unencrypted connections"); + continue + } + "AES128" | "AES128_GCM" | "AES_128" | "AES_128_GCM" => &aead::AES_128_GCM, + "AES256" | "AES256_GCM" | "AES_256" | "AES_256_GCM" => &aead::AES_256_GCM, + "CHACHA" | "CHACHA20" | "CHACHA20_POLY1305" => &aead::CHACHA20_POLY1305, + _ => return Err(Error::InvalidConfig("Unknown crypto method")) + }; + let speed = test_speed(algo, &duration); + algos.algorithm_speeds.push((algo, speed as f32)); + speeds.push((name, speed as f32)); + } + if !speeds.is_empty() { + info!( + "Crypto speeds: {}", + speeds.into_iter().map(|(a, s)| format!("{}: {:.1} MiB/s", a, s)).collect::>().join(", ") + ); + } + Ok(Self { + node_id, + key_pair: Arc::new(key_pair), + trusted_keys: trusted_keys.into_boxed_slice().into(), + algorithms: algos + }) + } + + pub fn generate_keypair(password: Option<&str>) -> (String, String) { + let mut bytes = [0; 32]; + match password { + None => { + let rng = SystemRandom::new(); + rng.fill(&mut bytes).unwrap(); + } + Some(password) => { + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA256, + NonZeroU32::new(4096).unwrap(), + SALT, + password.as_bytes(), + &mut bytes + ); + } + } + let keypair = Ed25519KeyPair::from_seed_unchecked(&bytes).unwrap(); + let privkey = to_base62(&bytes); + let pubkey = to_base62(keypair.public_key().as_ref()); + (privkey, pubkey) + } + + fn keypair_from_password(password: &str) -> Ed25519KeyPair { + let mut key = [0; 32]; + pbkdf2::derive(pbkdf2::PBKDF2_HMAC_SHA256, NonZeroU32::new(4096).unwrap(), SALT, password.as_bytes(), &mut key); + Ed25519KeyPair::from_seed_unchecked(&key).unwrap() + } + + fn parse_keypair(privkey: &str, pubkey: &str) -> Result { + let privkey = from_base62(privkey).map_err(|_| Error::InvalidConfig("Failed to parse private key"))?; + let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?; + let keypair = Ed25519KeyPair::from_seed_and_public_key(&privkey, &pubkey) + .map_err(|_| Error::InvalidConfig("Keys rejected by crypto library"))?; + Ok(keypair) + } + + fn parse_private_key(privkey: &str) -> Result { + let privkey = from_base62(privkey).map_err(|_| Error::InvalidConfig("Failed to parse private key"))?; + let keypair = Ed25519KeyPair::from_seed_unchecked(&privkey) + .map_err(|_| Error::InvalidConfig("Key rejected by crypto library"))?; + Ok(keypair) + } + + fn parse_public_key(pubkey: &str) -> Result { + let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?; + if pubkey.len() != ED25519_PUBLIC_KEY_LEN { + return Err(Error::InvalidConfig("Failed to parse public key")) + } + let mut result = [0; ED25519_PUBLIC_KEY_LEN]; + result.clone_from_slice(&pubkey); + Ok(result) + } + + pub fn peer_instance(&self, payload: P) -> PeerCrypto

{ + PeerCrypto::new( + self.node_id, + payload, + self.key_pair.clone(), + self.trusted_keys.clone(), + self.algorithms.clone() + ) + } +} + + +#[derive(Debug, PartialEq)] +pub enum MessageResult { + Message(u8), + Initialized(P), + InitializedWithReply(P), + Reply, + None +} + + +pub struct PeerCrypto { + #[allow(dead_code)] + node_id: NodeId, + init: Option>, + rotation: Option, + unencrypted: bool, + core: Option, + rotate_counter: usize +} + +impl PeerCrypto

{ + pub fn new( + node_id: NodeId, init_payload: P, key_pair: Arc, trusted_keys: Arc<[Ed25519PublicKey]>, + algorithms: Algorithms + ) -> Self + { + Self { + node_id, + init: Some(InitState::new(node_id, init_payload, key_pair, trusted_keys, algorithms)), + rotation: None, + unencrypted: false, + core: None, + rotate_counter: 0 + } + } + + fn get_init(&mut self) -> Result<&mut InitState

, Error> { + if let Some(init) = &mut self.init { + Ok(init) + } else { + Err(Error::InvalidCryptoState("Initialization already finished")) + } + } + + fn get_core(&mut self) -> Result<&mut CryptoCore, Error> { + if let Some(core) = &mut self.core { + Ok(core) + } else { + Err(Error::InvalidCryptoState("Crypto core not ready yet")) + } + } + + fn get_rotation(&mut self) -> Result<&mut RotationState, Error> { + if let Some(rotation) = &mut self.rotation { + Ok(rotation) + } else { + Err(Error::InvalidCryptoState("Key rotation not initialized")) + } + } + + pub fn initialize(&mut self, out: &mut MsgBuffer) -> Result<(), Error> { + let init = self.get_init()?; + if init.stage() != init::STAGE_PING { + Err(Error::InvalidCryptoState("Initialization already ongoing")) + } else { + init.send_ping(out); + out.prepend_byte(INIT_MESSAGE_FIRST_BYTE); + Ok(()) + } + } + + pub fn has_init(&self) -> bool { + self.init.is_some() + } + + pub fn is_ready(&self) -> bool { + self.core.is_some() + } + + pub fn algorithm_name(&self) -> &'static str { + if let Some(ref core) = self.core { + let algo = core.algorithm(); + if algo == &aead::CHACHA20_POLY1305 { + "CHACHA20" + } else if algo == &aead::AES_128_GCM { + "AES128" + } else if algo == &aead::AES_256_GCM { + "AES256" + } else { + unreachable!() + } + } else { + "PLAIN" + } + } + + fn handle_init_message(&mut self, buffer: &mut MsgBuffer) -> Result, Error> { + let result = self.get_init()?.handle_init(buffer)?; + if !buffer.is_empty() { + buffer.prepend_byte(INIT_MESSAGE_FIRST_BYTE); + } + match result { + InitResult::Continue => Ok(MessageResult::Reply), + InitResult::Success { peer_payload, is_initiator } => { + self.core = self.get_init()?.take_core(); + if self.core.is_none() { + self.unencrypted = true; + } + if self.get_init()?.stage() == init::CLOSING { + self.init = None + } + if self.core.is_some() { + self.rotation = Some(RotationState::new(!is_initiator, buffer)); + } + if !is_initiator { + if self.unencrypted { + return Ok(MessageResult::Initialized(peer_payload)) + } + assert!(!buffer.is_empty()); + buffer.prepend_byte(MESSAGE_TYPE_ROTATION); + self.encrypt_message(buffer)?; + } + Ok(MessageResult::InitializedWithReply(peer_payload)) + } + } + } + + fn handle_rotate_message(&mut self, data: &[u8]) -> Result<(), Error> { + if self.unencrypted { + return Ok(()) + } + if let Some(rot) = self.get_rotation()?.handle_message(data)? { + let core = self.get_core()?; + let algo = core.algorithm(); + let key = LessSafeKey::new(UnboundKey::new(algo, &rot.key[..algo.key_len()]).unwrap()); + core.rotate_key(key, rot.id, rot.use_for_sending); + } + Ok(()) + } + + fn encrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { + if self.unencrypted { + return Ok(()) + } + self.get_core()?.encrypt(buffer); + Ok(()) + } + + fn decrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { + // HOT PATH + if self.unencrypted { + return Ok(()) + } + self.get_core()?.decrypt(buffer) + } + + pub fn handle_message(&mut self, buffer: &mut MsgBuffer) -> Result, Error> { + // HOT PATH + if buffer.is_empty() { + return Err(Error::InvalidCryptoState("No message in buffer")) + } + if is_init_message(buffer.buffer()) { + // COLD PATH + debug!("Received init message"); + buffer.take_prefix(); + self.handle_init_message(buffer) + } else { + // HOT PATH + debug!("Received encrypted message"); + self.decrypt_message(buffer)?; + let msg_type = buffer.take_prefix(); + if msg_type == MESSAGE_TYPE_ROTATION { + // COLD PATH + debug!("Received rotation message"); + self.handle_rotate_message(buffer.buffer())?; + buffer.clear(); + Ok(MessageResult::None) + } else { + Ok(MessageResult::Message(msg_type)) + } + } + } + + pub fn send_message(&mut self, type_: u8, buffer: &mut MsgBuffer) -> Result<(), Error> { + // HOT PATH + assert_ne!(type_, MESSAGE_TYPE_ROTATION); + buffer.prepend_byte(type_); + self.encrypt_message(buffer) + } + + pub fn every_second(&mut self, out: &mut MsgBuffer) -> Result, Error> { + out.clear(); + if let Some(ref mut core) = self.core { + core.every_second() + } + if let Some(ref mut init) = self.init { + init.every_second(out)?; + } + if self.init.as_ref().map(|i| i.stage()).unwrap_or(CLOSING) == CLOSING { + self.init = None + } + if !out.is_empty() { + out.prepend_byte(INIT_MESSAGE_FIRST_BYTE); + return Ok(MessageResult::Reply) + } + if let Some(ref mut rotate) = self.rotation { + self.rotate_counter += 1; + if self.rotate_counter >= ROTATE_INTERVAL { + self.rotate_counter = 0; + if let Some(rot) = rotate.cycle(out) { + let core = self.get_core()?; + let algo = core.algorithm(); + let key = LessSafeKey::new(UnboundKey::new(algo, &rot.key[..algo.key_len()]).unwrap()); + core.rotate_key(key, rot.id, rot.use_for_sending); + } + if !out.is_empty() { + out.prepend_byte(MESSAGE_TYPE_ROTATION); + self.encrypt_message(out)?; + return Ok(MessageResult::Reply) + } + } + } + Ok(MessageResult::None) + } +} + +pub fn is_init_message(msg: &[u8]) -> bool { + // HOT PATH + !msg.is_empty() && msg[0] == INIT_MESSAGE_FIRST_BYTE +} + + +#[cfg(test)] +mod tests { + use super::*; + + use crate::types::NODE_ID_BYTES; + + fn create_node(config: &Config) -> PeerCrypto> { + let rng = SystemRandom::new(); + let mut node_id = [0; NODE_ID_BYTES]; + rng.fill(&mut node_id).unwrap(); + let crypto = Crypto::new(node_id, config).unwrap(); + crypto.peer_instance(vec![]) + } + + #[test] + fn normal() { + let config = Config { password: Some("test".to_string()), ..Default::default() }; + let mut node1 = create_node(&config); + let mut node2 = create_node(&config); + let mut msg = MsgBuffer::new(16); + + node1.initialize(&mut msg).unwrap(); + assert!(!msg.is_empty()); + + debug!("Node1 -> Node2"); + let res = node2.handle_message(&mut msg).unwrap(); + assert_eq!(res, MessageResult::Reply); + assert!(!msg.is_empty()); + + debug!("Node1 <- Node2"); + let res = node1.handle_message(&mut msg).unwrap(); + assert_eq!(res, MessageResult::InitializedWithReply(vec![])); + assert!(!msg.is_empty()); + + debug!("Node1 -> Node2"); + let res = node2.handle_message(&mut msg).unwrap(); + assert_eq!(res, MessageResult::InitializedWithReply(vec![])); + assert!(!msg.is_empty()); + + debug!("Node1 <- Node2"); + let res = node1.handle_message(&mut msg).unwrap(); + assert_eq!(res, MessageResult::None); + assert!(msg.is_empty()); + + let mut buffer = MsgBuffer::new(16); + let rng = SystemRandom::new(); + buffer.set_length(1000); + rng.fill(buffer.message_mut()).unwrap(); + for _ in 0..1000 { + node1.send_message(1, &mut buffer).unwrap(); + let res = node2.handle_message(&mut buffer).unwrap(); + assert_eq!(res, MessageResult::Message(1)); + + match node1.every_second(&mut msg).unwrap() { + MessageResult::None => (), + MessageResult::Reply => { + let res = node2.handle_message(&mut msg).unwrap(); + assert_eq!(res, MessageResult::None); + } + other => assert_eq!(other, MessageResult::None) + } + match node2.every_second(&mut msg).unwrap() { + MessageResult::None => (), + MessageResult::Reply => { + let res = node1.handle_message(&mut msg).unwrap(); + assert_eq!(res, MessageResult::None); + } + other => assert_eq!(other, MessageResult::None) + } + } + } +} diff --git a/src/crypto/core.rs b/src/crypto/core.rs index df7e300..187c5b0 100644 --- a/src/crypto/core.rs +++ b/src/crypto/core.rs @@ -53,7 +53,7 @@ use std::{ time::{Duration, Instant} }; -use super::{Error, MsgBuffer}; +use crate::{error::Error, util::MsgBuffer}; const NONCE_LEN: usize = 12; diff --git a/src/crypto/init.rs b/src/crypto/init.rs index ed7a040..1790dc2 100644 --- a/src/crypto/init.rs +++ b/src/crypto/init.rs @@ -57,9 +57,9 @@ use super::{ core::{CryptoCore, EXTRA_LEN}, - Algorithms, EcdhPrivateKey, EcdhPublicKey, Ed25519PublicKey, Error, MsgBuffer, Payload + Algorithms, EcdhPrivateKey, EcdhPublicKey, Ed25519PublicKey, Payload }; -use crate::types::NodeId; +use crate::{error::Error, types::NodeId, util::MsgBuffer}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use ring::{ aead::{Algorithm, LessSafeKey, UnboundKey, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305}, @@ -404,8 +404,7 @@ impl InitState

{ pub fn new( node_id: NodeId, payload: P, key_pair: Arc, trusted_keys: Arc<[Ed25519PublicKey]>, algorithms: Algorithms - ) -> Self - { + ) -> Self { let mut hash = [0; SALTED_NODE_ID_HASH_LEN]; let rng = SystemRandom::new(); rng.fill(&mut hash[0..4]).unwrap(); diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 8d841a5..584ee60 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -2,507 +2,10 @@ // Copyright (C) 2015-2021 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) +mod common; mod core; mod init; mod rotate; -pub use self::core::{EXTRA_LEN, TAG_LEN}; -use self::{ - core::{test_speed, CryptoCore}, - init::{InitResult, InitState, CLOSING}, - rotate::RotationState -}; -use crate::{ - error::Error, - types::NodeId, - util::{from_base62, to_base62, MsgBuffer} -}; -use ring::{ - aead::{self, Algorithm, LessSafeKey, UnboundKey}, - agreement::{EphemeralPrivateKey, UnparsedPublicKey}, - pbkdf2, - rand::{SecureRandom, SystemRandom}, - signature::{Ed25519KeyPair, KeyPair, ED25519_PUBLIC_KEY_LEN} -}; -use smallvec::{smallvec, SmallVec}; -use std::{fmt::Debug, io::Read, num::NonZeroU32, sync::Arc, time::Duration}; -use thiserror::Error; - - -const SALT: &[u8; 32] = b"vpncloudVPNCLOUDvpncl0udVpnCloud"; -const INIT_MESSAGE_FIRST_BYTE: u8 = 0xff; -const MESSAGE_TYPE_ROTATION: u8 = 0x10; - -pub type Ed25519PublicKey = [u8; ED25519_PUBLIC_KEY_LEN]; -pub type EcdhPublicKey = UnparsedPublicKey>; -pub type EcdhPrivateKey = EphemeralPrivateKey; -pub type Key = SmallVec<[u8; 32]>; - - -const DEFAULT_ALGORITHMS: [&str; 3] = ["AES128", "AES256", "CHACHA20"]; - -#[cfg(test)] -const SPEED_TEST_TIME: f32 = 0.02; -#[cfg(not(test))] -const SPEED_TEST_TIME: f32 = 0.1; - -const ROTATE_INTERVAL: usize = 120; - - -pub trait Payload: Debug + PartialEq + Sized { - fn write_to(&self, buffer: &mut MsgBuffer); - fn read_from(r: R) -> Result; -} - - -#[derive(Clone)] -pub struct Algorithms { - pub algorithm_speeds: SmallVec<[(&'static Algorithm, f32); 3]>, - pub allow_unencrypted: bool -} - -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case", deny_unknown_fields, default)] -pub struct Config { - pub password: Option, - pub private_key: Option, - pub public_key: Option, - pub trusted_keys: Vec, - pub algorithms: Vec -} - -pub struct Crypto { - node_id: NodeId, - key_pair: Arc, - trusted_keys: Arc<[Ed25519PublicKey]>, - algorithms: Algorithms -} - -impl Crypto { - pub fn new(node_id: NodeId, config: &Config) -> Result { - let key_pair = if let Some(priv_key) = &config.private_key { - if let Some(pub_key) = &config.public_key { - Self::parse_keypair(priv_key, pub_key)? - } else { - Self::parse_private_key(priv_key)? - } - } else if let Some(password) = &config.password { - Self::keypair_from_password(password) - } else { - return Err(Error::InvalidConfig("Either private_key or password must be set")) - }; - let mut trusted_keys = vec![]; - for tn in &config.trusted_keys { - trusted_keys.push(Self::parse_public_key(tn)?); - } - if trusted_keys.is_empty() { - info!("Trusted keys not set, trusting only own public key"); - let mut key = [0; ED25519_PUBLIC_KEY_LEN]; - key.clone_from_slice(key_pair.public_key().as_ref()); - trusted_keys.push(key); - } - let mut algos = Algorithms { algorithm_speeds: smallvec![], allow_unencrypted: false }; - let algorithms = config.algorithms.iter().map(|a| a as &str).collect::>(); - let allowed = if algorithms.is_empty() { &DEFAULT_ALGORITHMS } else { &algorithms as &[&str] }; - let duration = Duration::from_secs_f32(SPEED_TEST_TIME); - let mut speeds = Vec::new(); - for name in allowed { - let algo = match &name.to_uppercase() as &str { - "UNENCRYPTED" | "NONE" | "PLAIN" => { - algos.allow_unencrypted = true; - warn!("Crypto settings allow unencrypted connections"); - continue - } - "AES128" | "AES128_GCM" | "AES_128" | "AES_128_GCM" => &aead::AES_128_GCM, - "AES256" | "AES256_GCM" | "AES_256" | "AES_256_GCM" => &aead::AES_256_GCM, - "CHACHA" | "CHACHA20" | "CHACHA20_POLY1305" => &aead::CHACHA20_POLY1305, - _ => return Err(Error::InvalidConfig("Unknown crypto method")) - }; - let speed = test_speed(algo, &duration); - algos.algorithm_speeds.push((algo, speed as f32)); - speeds.push((name, speed as f32)); - } - if !speeds.is_empty() { - info!( - "Crypto speeds: {}", - speeds.into_iter().map(|(a, s)| format!("{}: {:.1} MiB/s", a, s)).collect::>().join(", ") - ); - } - Ok(Self { - node_id, - key_pair: Arc::new(key_pair), - trusted_keys: trusted_keys.into_boxed_slice().into(), - algorithms: algos - }) - } - - pub fn generate_keypair(password: Option<&str>) -> (String, String) { - let mut bytes = [0; 32]; - match password { - None => { - let rng = SystemRandom::new(); - rng.fill(&mut bytes).unwrap(); - } - Some(password) => { - pbkdf2::derive( - pbkdf2::PBKDF2_HMAC_SHA256, - NonZeroU32::new(4096).unwrap(), - SALT, - password.as_bytes(), - &mut bytes - ); - } - } - let keypair = Ed25519KeyPair::from_seed_unchecked(&bytes).unwrap(); - let privkey = to_base62(&bytes); - let pubkey = to_base62(keypair.public_key().as_ref()); - (privkey, pubkey) - } - - fn keypair_from_password(password: &str) -> Ed25519KeyPair { - let mut key = [0; 32]; - pbkdf2::derive(pbkdf2::PBKDF2_HMAC_SHA256, NonZeroU32::new(4096).unwrap(), SALT, password.as_bytes(), &mut key); - Ed25519KeyPair::from_seed_unchecked(&key).unwrap() - } - - fn parse_keypair(privkey: &str, pubkey: &str) -> Result { - let privkey = from_base62(privkey).map_err(|_| Error::InvalidConfig("Failed to parse private key"))?; - let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?; - let keypair = Ed25519KeyPair::from_seed_and_public_key(&privkey, &pubkey) - .map_err(|_| Error::InvalidConfig("Keys rejected by crypto library"))?; - Ok(keypair) - } - - fn parse_private_key(privkey: &str) -> Result { - let privkey = from_base62(privkey).map_err(|_| Error::InvalidConfig("Failed to parse private key"))?; - let keypair = Ed25519KeyPair::from_seed_unchecked(&privkey) - .map_err(|_| Error::InvalidConfig("Key rejected by crypto library"))?; - Ok(keypair) - } - - fn parse_public_key(pubkey: &str) -> Result { - let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?; - if pubkey.len() != ED25519_PUBLIC_KEY_LEN { - return Err(Error::InvalidConfig("Failed to parse public key")) - } - let mut result = [0; ED25519_PUBLIC_KEY_LEN]; - result.clone_from_slice(&pubkey); - Ok(result) - } - - pub fn peer_instance(&self, payload: P) -> PeerCrypto

{ - PeerCrypto::new( - self.node_id, - payload, - self.key_pair.clone(), - self.trusted_keys.clone(), - self.algorithms.clone() - ) - } -} - - -#[derive(Debug, PartialEq)] -pub enum MessageResult { - Message(u8), - Initialized(P), - InitializedWithReply(P), - Reply, - None -} - - -pub struct PeerCrypto { - #[allow(dead_code)] - node_id: NodeId, - init: Option>, - rotation: Option, - unencrypted: bool, - core: Option, - rotate_counter: usize -} - -impl PeerCrypto

{ - pub fn new( - node_id: NodeId, init_payload: P, key_pair: Arc, trusted_keys: Arc<[Ed25519PublicKey]>, - algorithms: Algorithms - ) -> Self - { - Self { - node_id, - init: Some(InitState::new(node_id, init_payload, key_pair, trusted_keys, algorithms)), - rotation: None, - unencrypted: false, - core: None, - rotate_counter: 0 - } - } - - fn get_init(&mut self) -> Result<&mut InitState

, Error> { - if let Some(init) = &mut self.init { - Ok(init) - } else { - Err(Error::InvalidCryptoState("Initialization already finished")) - } - } - - fn get_core(&mut self) -> Result<&mut CryptoCore, Error> { - if let Some(core) = &mut self.core { - Ok(core) - } else { - Err(Error::InvalidCryptoState("Crypto core not ready yet")) - } - } - - fn get_rotation(&mut self) -> Result<&mut RotationState, Error> { - if let Some(rotation) = &mut self.rotation { - Ok(rotation) - } else { - Err(Error::InvalidCryptoState("Key rotation not initialized")) - } - } - - pub fn initialize(&mut self, out: &mut MsgBuffer) -> Result<(), Error> { - let init = self.get_init()?; - if init.stage() != init::STAGE_PING { - Err(Error::InvalidCryptoState("Initialization already ongoing")) - } else { - init.send_ping(out); - out.prepend_byte(INIT_MESSAGE_FIRST_BYTE); - Ok(()) - } - } - - pub fn has_init(&self) -> bool { - self.init.is_some() - } - - pub fn is_ready(&self) -> bool { - self.core.is_some() - } - - pub fn algorithm_name(&self) -> &'static str { - if let Some(ref core) = self.core { - let algo = core.algorithm(); - if algo == &aead::CHACHA20_POLY1305 { - "CHACHA20" - } else if algo == &aead::AES_128_GCM { - "AES128" - } else if algo == &aead::AES_256_GCM { - "AES256" - } else { - unreachable!() - } - } else { - "PLAIN" - } - } - - fn handle_init_message(&mut self, buffer: &mut MsgBuffer) -> Result, Error> { - let result = self.get_init()?.handle_init(buffer)?; - if !buffer.is_empty() { - buffer.prepend_byte(INIT_MESSAGE_FIRST_BYTE); - } - match result { - InitResult::Continue => Ok(MessageResult::Reply), - InitResult::Success { peer_payload, is_initiator } => { - self.core = self.get_init()?.take_core(); - if self.core.is_none() { - self.unencrypted = true; - } - if self.get_init()?.stage() == init::CLOSING { - self.init = None - } - if self.core.is_some() { - self.rotation = Some(RotationState::new(!is_initiator, buffer)); - } - if !is_initiator { - if self.unencrypted { - return Ok(MessageResult::Initialized(peer_payload)) - } - assert!(!buffer.is_empty()); - buffer.prepend_byte(MESSAGE_TYPE_ROTATION); - self.encrypt_message(buffer)?; - } - Ok(MessageResult::InitializedWithReply(peer_payload)) - } - } - } - - fn handle_rotate_message(&mut self, data: &[u8]) -> Result<(), Error> { - if self.unencrypted { - return Ok(()) - } - if let Some(rot) = self.get_rotation()?.handle_message(data)? { - let core = self.get_core()?; - let algo = core.algorithm(); - let key = LessSafeKey::new(UnboundKey::new(algo, &rot.key[..algo.key_len()]).unwrap()); - core.rotate_key(key, rot.id, rot.use_for_sending); - } - Ok(()) - } - - fn encrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { - if self.unencrypted { - return Ok(()) - } - self.get_core()?.encrypt(buffer); - Ok(()) - } - - fn decrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { - // HOT PATH - if self.unencrypted { - return Ok(()) - } - self.get_core()?.decrypt(buffer) - } - - pub fn handle_message(&mut self, buffer: &mut MsgBuffer) -> Result, Error> { - // HOT PATH - if buffer.is_empty() { - return Err(Error::InvalidCryptoState("No message in buffer")) - } - if is_init_message(buffer.buffer()) { - // COLD PATH - debug!("Received init message"); - buffer.take_prefix(); - self.handle_init_message(buffer) - } else { - // HOT PATH - debug!("Received encrypted message"); - self.decrypt_message(buffer)?; - let msg_type = buffer.take_prefix(); - if msg_type == MESSAGE_TYPE_ROTATION { - // COLD PATH - debug!("Received rotation message"); - self.handle_rotate_message(buffer.buffer())?; - buffer.clear(); - Ok(MessageResult::None) - } else { - Ok(MessageResult::Message(msg_type)) - } - } - } - - pub fn send_message(&mut self, type_: u8, buffer: &mut MsgBuffer) -> Result<(), Error> { - // HOT PATH - assert_ne!(type_, MESSAGE_TYPE_ROTATION); - buffer.prepend_byte(type_); - self.encrypt_message(buffer) - } - - pub fn every_second(&mut self, out: &mut MsgBuffer) -> Result, Error> { - out.clear(); - if let Some(ref mut core) = self.core { - core.every_second() - } - if let Some(ref mut init) = self.init { - init.every_second(out)?; - } - if self.init.as_ref().map(|i| i.stage()).unwrap_or(CLOSING) == CLOSING { - self.init = None - } - if !out.is_empty() { - out.prepend_byte(INIT_MESSAGE_FIRST_BYTE); - return Ok(MessageResult::Reply) - } - if let Some(ref mut rotate) = self.rotation { - self.rotate_counter += 1; - if self.rotate_counter >= ROTATE_INTERVAL { - self.rotate_counter = 0; - if let Some(rot) = rotate.cycle(out) { - let core = self.get_core()?; - let algo = core.algorithm(); - let key = LessSafeKey::new(UnboundKey::new(algo, &rot.key[..algo.key_len()]).unwrap()); - core.rotate_key(key, rot.id, rot.use_for_sending); - } - if !out.is_empty() { - out.prepend_byte(MESSAGE_TYPE_ROTATION); - self.encrypt_message(out)?; - return Ok(MessageResult::Reply) - } - } - } - Ok(MessageResult::None) - } -} - -pub fn is_init_message(msg: &[u8]) -> bool { - // HOT PATH - !msg.is_empty() && msg[0] == INIT_MESSAGE_FIRST_BYTE -} - - -#[cfg(test)] -mod tests { - use super::*; - - use crate::types::NODE_ID_BYTES; - - fn create_node(config: &Config) -> PeerCrypto> { - let rng = SystemRandom::new(); - let mut node_id = [0; NODE_ID_BYTES]; - rng.fill(&mut node_id).unwrap(); - let crypto = Crypto::new(node_id, config).unwrap(); - crypto.peer_instance(vec![]) - } - - #[test] - fn normal() { - let config = Config { password: Some("test".to_string()), ..Default::default() }; - let mut node1 = create_node(&config); - let mut node2 = create_node(&config); - let mut msg = MsgBuffer::new(16); - - node1.initialize(&mut msg).unwrap(); - assert!(!msg.is_empty()); - - debug!("Node1 -> Node2"); - let res = node2.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::Reply); - assert!(!msg.is_empty()); - - debug!("Node1 <- Node2"); - let res = node1.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::InitializedWithReply(vec![])); - assert!(!msg.is_empty()); - - debug!("Node1 -> Node2"); - let res = node2.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::InitializedWithReply(vec![])); - assert!(!msg.is_empty()); - - debug!("Node1 <- Node2"); - let res = node1.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::None); - assert!(msg.is_empty()); - - let mut buffer = MsgBuffer::new(16); - let rng = SystemRandom::new(); - buffer.set_length(1000); - rng.fill(buffer.message_mut()).unwrap(); - for _ in 0..1000 { - node1.send_message(1, &mut buffer).unwrap(); - let res = node2.handle_message(&mut buffer).unwrap(); - assert_eq!(res, MessageResult::Message(1)); - - match node1.every_second(&mut msg).unwrap() { - MessageResult::None => (), - MessageResult::Reply => { - let res = node2.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::None); - } - other => assert_eq!(other, MessageResult::None) - } - match node2.every_second(&mut msg).unwrap() { - MessageResult::None => (), - MessageResult::Reply => { - let res = node1.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::None); - } - other => assert_eq!(other, MessageResult::None) - } - } - } -} +pub use common::*; +pub use self::core::{EXTRA_LEN, TAG_LEN}; \ No newline at end of file diff --git a/src/crypto/rotate.rs b/src/crypto/rotate.rs index 5d99215..e47aa59 100644 --- a/src/crypto/rotate.rs +++ b/src/crypto/rotate.rs @@ -29,7 +29,8 @@ // // The whole communication is sent via the crypto stream and is therefore encrypted and protected against tampering. -use super::{Error, Key, MsgBuffer}; +use super::Key; +use crate::{error::Error, util::MsgBuffer}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use ring::{ agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519}, diff --git a/src/device.rs b/src/device.rs index a3a5dc7..42df878 100644 --- a/src/device.rs +++ b/src/device.rs @@ -344,7 +344,7 @@ impl Device for MockDevice { } fn write(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> { - self.outbound.push_back(buffer.message().to_owned()); + self.outbound.push_back(buffer.message().into()); Ok(()) } @@ -355,7 +355,7 @@ impl Device for MockDevice { impl Default for MockDevice { fn default() -> Self { - Self { outbound: VecDeque::new(), inbound: VecDeque::new() } + Self { outbound: VecDeque::with_capacity(10), inbound: VecDeque::with_capacity(10) } } } diff --git a/src/net.rs b/src/net.rs index 7fbdaff..7356158 100644 --- a/src/net.rs +++ b/src/net.rs @@ -93,8 +93,8 @@ impl MockSocket { nat: Self::get_nat(), nat_peers: HashMap::new(), address, - outbound: VecDeque::new(), - inbound: VecDeque::new() + outbound: VecDeque::with_capacity(10), + inbound: VecDeque::with_capacity(10) } } @@ -149,7 +149,7 @@ impl Socket for MockSocket { } fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result { - self.outbound.push_back((addr, data.to_owned())); + self.outbound.push_back((addr, data.into())); if self.nat { self.nat_peers.insert(addr, MockTimeSource::now() + 300); } diff --git a/src/tests/common.rs b/src/tests/common.rs new file mode 100644 index 0000000..b897b1f --- /dev/null +++ b/src/tests/common.rs @@ -0,0 +1,214 @@ +// VpnCloud - Peer-to-Peer VPN +// Copyright (C) 2015-2021 Dennis Schwerdel +// This software is licensed under GPL-3 or newer (see LICENSE.md) + +use std::{ + collections::{HashMap, VecDeque}, + io::Write, + net::SocketAddr, + sync::{ + atomic::{AtomicUsize, Ordering}, + Once + } +}; + +pub use crate::{ + cloud::GenericCloud, + config::{Config, CryptoConfig}, + device::{MockDevice, Type}, + net::MockSocket, + payload::{Frame, Packet, Protocol}, + types::Range, + util::{MockTimeSource, Time, TimeSource} +}; + + +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); + }) +} + +static CURRENT_NODE: AtomicUsize = AtomicUsize::new(0); + +struct DebugLogger; + +impl DebugLogger { + pub fn set_node(node: usize) { + CURRENT_NODE.store(node, Ordering::SeqCst); + } +} + +impl log::Log for DebugLogger { + #[inline] + fn enabled(&self, metadata: &log::Metadata) -> bool { + log::max_level() > metadata.level() + } + + #[inline] + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + eprintln!("Node {} - {} - {}", CURRENT_NODE.load(Ordering::SeqCst), record.level(), record.args()); + } + } + + #[inline] + fn flush(&self) { + std::io::stderr().flush().expect("Failed to flush") + } +} + + +type TestNode

= GenericCloud; + +pub struct Simulator { + next_port: u16, + nodes: HashMap>, + messages: VecDeque<(SocketAddr, SocketAddr, Vec)> +} + +pub type TapSimulator = Simulator; +#[allow(dead_code)] +pub type TunSimulator = Simulator; + + +impl Simulator

{ + pub fn new() -> Self { + init_debug_logger(); + MockTimeSource::set_time(0); + Self { next_port: 1, nodes: HashMap::default(), messages: VecDeque::with_capacity(10) } + } + + pub fn add_node(&mut self, nat: bool, config: &Config) -> SocketAddr { + let mut config = config.clone(); + MockSocket::set_nat(nat); + config.listen = format!("[::]:{}", self.next_port); + let addr = config.listen.parse::().unwrap(); + if config.crypto.password.is_none() && config.crypto.private_key.is_none() { + config.crypto.password = Some("test123".to_string()) + } + DebugLogger::set_node(self.next_port as usize); + self.next_port += 1; + let node = TestNode::new(&config, MockSocket::new(addr), MockDevice::new(), None, None); + DebugLogger::set_node(0); + self.nodes.insert(addr, node); + addr + } + + #[allow(dead_code)] + pub fn get_node(&mut self, addr: SocketAddr) -> &mut TestNode

{ + let node = self.nodes.get_mut(&addr).unwrap(); + DebugLogger::set_node(node.get_num()); + node + } + + pub fn simulate_next_message(&mut self) { + if let Some((src, dst, data)) = self.messages.pop_front() { + if let Some(node) = self.nodes.get_mut(&dst) { + if node.socket().put_inbound(src, data) { + DebugLogger::set_node(node.get_num()); + node.trigger_socket_event(); + DebugLogger::set_node(0); + let sock = node.socket(); + let src = dst; + while let Some((dst, data)) = sock.pop_outbound() { + self.messages.push_back((src, dst, data)); + } + } + } else { + warn!("Message to unknown node {}", dst); + } + } + } + + pub fn simulate_all_messages(&mut self) { + while !self.messages.is_empty() { + self.simulate_next_message() + } + } + + pub fn trigger_node_housekeep(&mut self, addr: SocketAddr) { + let node = self.nodes.get_mut(&addr).unwrap(); + DebugLogger::set_node(node.get_num()); + node.trigger_housekeep(); + DebugLogger::set_node(0); + let sock = node.socket(); + while let Some((dst, data)) = sock.pop_outbound() { + self.messages.push_back((addr, dst, data)); + } + } + + pub fn trigger_housekeep(&mut self) { + for (src, node) in &mut self.nodes { + DebugLogger::set_node(node.get_num()); + node.trigger_housekeep(); + DebugLogger::set_node(0); + let sock = node.socket(); + while let Some((dst, data)) = sock.pop_outbound() { + self.messages.push_back((*src, dst, data)); + } + } + } + + pub fn set_time(&mut self, time: Time) { + MockTimeSource::set_time(time); + } + + pub fn simulate_time(&mut self, time: Time) { + let mut t = MockTimeSource::now(); + while t < time { + t += 1; + self.set_time(t); + self.trigger_housekeep(); + self.simulate_all_messages(); + } + } + + pub fn connect(&mut self, src: SocketAddr, dst: SocketAddr) { + let node = self.nodes.get_mut(&src).unwrap(); + DebugLogger::set_node(node.get_num()); + node.connect(dst).unwrap(); + DebugLogger::set_node(0); + let sock = node.socket(); + while let Some((dst, data)) = sock.pop_outbound() { + self.messages.push_back((src, dst, data)); + } + } + + pub fn is_connected(&self, src: SocketAddr, dst: SocketAddr) -> bool { + self.nodes.get(&src).unwrap().is_connected(&dst) + } + + #[allow(dead_code)] + pub fn node_addresses(&self) -> Vec { + self.nodes.keys().copied().collect() + } + + #[allow(dead_code)] + pub fn message_count(&self) -> usize { + self.messages.len() + } + + pub fn put_payload(&mut self, addr: SocketAddr, data: Vec) { + let node = self.nodes.get_mut(&addr).unwrap(); + node.device().put_inbound(data); + DebugLogger::set_node(node.get_num()); + node.trigger_device_event(); + DebugLogger::set_node(0); + let sock = node.socket(); + while let Some((dst, data)) = sock.pop_outbound() { + self.messages.push_back((addr, dst, data)); + } + } + + pub fn pop_payload(&mut self, node: SocketAddr) -> Option> { + self.nodes.get_mut(&node).unwrap().device().pop_outbound() + } + + pub fn drop_message(&mut self) { + self.messages.pop_front(); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f1e8747..16e2f86 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -2,217 +2,7 @@ // Copyright (C) 2015-2021 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) +mod common; mod nat; mod payload; -mod peers; - -use std::{ - collections::{HashMap, VecDeque}, - io::Write, - net::SocketAddr, - sync::{ - atomic::{AtomicUsize, Ordering}, - Once - } -}; - -pub use super::{ - cloud::GenericCloud, - config::{Config, CryptoConfig}, - device::{MockDevice, Type}, - net::MockSocket, - payload::{Frame, Packet, Protocol}, - types::Range, - util::{MockTimeSource, Time, TimeSource} -}; - - -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); - }) -} - -static CURRENT_NODE: AtomicUsize = AtomicUsize::new(0); - -struct DebugLogger; - -impl DebugLogger { - pub fn set_node(node: usize) { - CURRENT_NODE.store(node, Ordering::SeqCst); - } -} - -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!("Node {} - {} - {}", CURRENT_NODE.load(Ordering::SeqCst), record.level(), record.args()); - } - } - - #[inline] - fn flush(&self) { - std::io::stderr().flush().expect("Failed to flush") - } -} - - -type TestNode

= GenericCloud; - -pub struct Simulator { - next_port: u16, - nodes: HashMap>, - messages: VecDeque<(SocketAddr, SocketAddr, Vec)> -} - -pub type TapSimulator = Simulator; -#[allow(dead_code)] -pub type TunSimulator = Simulator; - - -impl Simulator

{ - pub fn new() -> Self { - init_debug_logger(); - MockTimeSource::set_time(0); - Self { next_port: 1, nodes: HashMap::default(), messages: VecDeque::default() } - } - - pub fn add_node(&mut self, nat: bool, config: &Config) -> SocketAddr { - let mut config = config.clone(); - MockSocket::set_nat(nat); - config.listen = format!("[::]:{}", self.next_port); - let addr = config.listen.parse::().unwrap(); - if config.crypto.password.is_none() && config.crypto.private_key.is_none() { - config.crypto.password = Some("test123".to_string()) - } - DebugLogger::set_node(self.next_port as usize); - self.next_port += 1; - let node = TestNode::new(&config, MockSocket::new(addr), MockDevice::new(), None, None); - DebugLogger::set_node(0); - self.nodes.insert(addr, node); - addr - } - - #[allow(dead_code)] - pub fn get_node(&mut self, addr: SocketAddr) -> &mut TestNode

{ - let node = self.nodes.get_mut(&addr).unwrap(); - DebugLogger::set_node(node.get_num()); - node - } - - pub fn simulate_next_message(&mut self) { - if let Some((src, dst, data)) = self.messages.pop_front() { - if let Some(node) = self.nodes.get_mut(&dst) { - if node.socket().put_inbound(src, data) { - DebugLogger::set_node(node.get_num()); - node.trigger_socket_event(); - DebugLogger::set_node(0); - let sock = node.socket(); - let src = dst; - while let Some((dst, data)) = sock.pop_outbound() { - self.messages.push_back((src, dst, data)); - } - } - } else { - warn!("Message to unknown node {}", dst); - } - } - } - - pub fn simulate_all_messages(&mut self) { - while !self.messages.is_empty() { - self.simulate_next_message() - } - } - - pub fn trigger_node_housekeep(&mut self, addr: SocketAddr) { - let node = self.nodes.get_mut(&addr).unwrap(); - DebugLogger::set_node(node.get_num()); - node.trigger_housekeep(); - DebugLogger::set_node(0); - let sock = node.socket(); - while let Some((dst, data)) = sock.pop_outbound() { - self.messages.push_back((addr, dst, data)); - } - } - - pub fn trigger_housekeep(&mut self) { - for (src, node) in &mut self.nodes { - DebugLogger::set_node(node.get_num()); - node.trigger_housekeep(); - DebugLogger::set_node(0); - let sock = node.socket(); - while let Some((dst, data)) = sock.pop_outbound() { - self.messages.push_back((*src, dst, data)); - } - } - } - - pub fn set_time(&mut self, time: Time) { - MockTimeSource::set_time(time); - } - - pub fn simulate_time(&mut self, time: Time) { - let mut t = MockTimeSource::now(); - while t < time { - t += 1; - self.set_time(t); - self.trigger_housekeep(); - self.simulate_all_messages(); - } - } - - pub fn connect(&mut self, src: SocketAddr, dst: SocketAddr) { - let node = self.nodes.get_mut(&src).unwrap(); - DebugLogger::set_node(node.get_num()); - node.connect(dst).unwrap(); - DebugLogger::set_node(0); - let sock = node.socket(); - while let Some((dst, data)) = sock.pop_outbound() { - self.messages.push_back((src, dst, data)); - } - } - - pub fn is_connected(&self, src: SocketAddr, dst: SocketAddr) -> bool { - self.nodes.get(&src).unwrap().is_connected(&dst) - } - - #[allow(dead_code)] - pub fn node_addresses(&self) -> Vec { - self.nodes.keys().copied().collect() - } - - #[allow(dead_code)] - pub fn message_count(&self) -> usize { - self.messages.len() - } - - pub fn put_payload(&mut self, addr: SocketAddr, data: Vec) { - let node = self.nodes.get_mut(&addr).unwrap(); - node.device().put_inbound(data); - DebugLogger::set_node(node.get_num()); - node.trigger_device_event(); - DebugLogger::set_node(0); - let sock = node.socket(); - while let Some((dst, data)) = sock.pop_outbound() { - self.messages.push_back((addr, dst, data)); - } - } - - pub fn pop_payload(&mut self, node: SocketAddr) -> Option> { - self.nodes.get_mut(&node).unwrap().device().pop_outbound() - } - - pub fn drop_message(&mut self) { - self.messages.pop_front(); - } -} +mod peers; \ No newline at end of file diff --git a/src/tests/nat.rs b/src/tests/nat.rs index cb08177..9593f6c 100644 --- a/src/tests/nat.rs +++ b/src/tests/nat.rs @@ -2,7 +2,7 @@ // Copyright (C) 2015-2021 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) -use super::*; +use super::common::*; #[test] fn connect_nat_2_peers() { diff --git a/src/tests/payload.rs b/src/tests/payload.rs index 1fd41d0..bb96d2e 100644 --- a/src/tests/payload.rs +++ b/src/tests/payload.rs @@ -2,7 +2,7 @@ // Copyright (C) 2015-2021 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) -use super::*; +use super::common::*; #[test] fn switch_delivers() { diff --git a/src/tests/peers.rs b/src/tests/peers.rs index 91d3c5b..8c56182 100644 --- a/src/tests/peers.rs +++ b/src/tests/peers.rs @@ -2,7 +2,7 @@ // Copyright (C) 2015-2021 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) -use super::*; +use super::common::*; #[test] fn direct_connect() {