From fecffcce95a95f588f58000962e2f90da7c08718 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Thu, 10 Jan 2019 19:36:50 +0100 Subject: [PATCH] Many changes for 0.9.0 --- CHANGELOG.md | 9 +++++++-- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/cloud.rs | 40 ++++++++++++++++++++++++---------------- src/config.rs | 19 +++++++++++++++++-- src/crypto.rs | 2 +- src/device.rs | 12 ++++++++++-- src/main.rs | 23 ++++++++++++----------- src/tests.rs | 16 ++++++++++++++-- src/usage.txt | 2 ++ 10 files changed, 90 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7613c3f..5aed0a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,16 @@ This project follows [semantic versioning](http://semver.org). -### UNRELEASED +### v0.9.0 (UNRELEASED) +- [added] Added keepalive option for nodes behind NAT +- [added] Added ability to write out statistics file with peers and traffic info +- [added] Added dummy device type that does not allocate an interface +- [changed] Using ring instead of libsodium +- [changed] Using PBKDF2 for shared keys (**incompatible**) - [fixed] Hashed magics now also consider first character (**incompatible**) -### v0.8.2 (2018-01-02) +### v0.8.2 (2019-01-02) - [changed] Using serde instead of rustc_serialize - [changed] Updated libsodium to 1.0.16 diff --git a/Cargo.lock b/Cargo.lock index 5ced9b0..b5c9b65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -961,7 +961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vpncloud" -version = "0.8.2" +version = "0.9.0" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index da4f22d..0ca09d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "vpncloud" -version = "0.8.2" -authors = ["Dennis Schwerdel "] +version = "0.9.0" +authors = ["Dennis Schwerdel "] build = "build.rs" license = "GPL-3.0" description = "Peer-to-peer VPN" diff --git a/src/cloud.rs b/src/cloud.rs index 01b7bda..85f30dd 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -19,8 +19,9 @@ use signal::{trap::Trap, Signal}; use rand::{prelude::*, random, thread_rng}; use net2::UdpBuilder; +use super::config::Config; use super::types::{Table, Protocol, Range, Error, HeaderMagic, NodeId}; -use super::device::Device; +use super::device::{Device, Type}; use super::udpmessage::{encode, decode, Message}; use super::crypto::Crypto; use super::port_forwarding::PortForwarding; @@ -214,26 +215,25 @@ pub struct GenericCloud { } impl GenericCloud { - #[allow(unknown_lints,clippy::too_many_arguments)] - pub fn new(magic: HeaderMagic, device: Device, listen: u16, table: T, - peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec, - crypto: Crypto, port_forwarding: Option, stats_file: Option + pub fn new(config: &Config, device: Device, table: T, + learning: bool, broadcast: bool, addresses: Vec, + crypto: Crypto, port_forwarding: Option ) -> Self { let socket4 = match UdpBuilder::new_v4().expect("Failed to obtain ipv4 socket builder") - .reuse_address(true).expect("Failed to set so_reuseaddr").bind(("0.0.0.0", listen)) { + .reuse_address(true).expect("Failed to set so_reuseaddr").bind(("0.0.0.0", config.port)) { Ok(socket) => socket, - Err(err) => fail!("Failed to open ipv4 address 0.0.0.0:{}: {}", listen, err) + Err(err) => fail!("Failed to open ipv4 address 0.0.0.0:{}: {}", config.port, err) }; let socket6 = match UdpBuilder::new_v6().expect("Failed to obtain ipv6 socket builder") .only_v6(true).expect("Failed to set only_v6") - .reuse_address(true).expect("Failed to set so_reuseaddr").bind(("::", listen)) { + .reuse_address(true).expect("Failed to set so_reuseaddr").bind(("::", config.port)) { Ok(socket) => socket, - Err(err) => fail!("Failed to open ipv6 address ::{}: {}", listen, err) + Err(err) => fail!("Failed to open ipv6 address ::{}: {}", config.port, err) }; GenericCloud{ - magic, + magic: config.get_magic(), node_id: random(), - peers: PeerList::new(peer_timeout), + peers: PeerList::new(config.peer_timeout), addresses, learning, broadcast, @@ -245,13 +245,13 @@ impl GenericCloud { device, crypto, next_peerlist: now(), - update_freq: peer_timeout/2-60, + update_freq: config.get_keepalive(), buffer_out: [0; 64*1024], next_housekeep: now(), next_stats_out: now() + STATS_INTERVAL, port_forwarding, traffic: TrafficStats::new(), - stats_file, + stats_file: config.stats_file.clone(), _dummy_p: PhantomData, } } @@ -635,7 +635,13 @@ impl GenericCloud { let device_fd = self.device.as_raw_fd(); try_fail!(poll_handle.register(socket4_fd, Flags::READ), "Failed to add ipv4 socket to poll handle: {}"); try_fail!(poll_handle.register(socket6_fd, Flags::READ), "Failed to add ipv6 socket to poll handle: {}"); - try_fail!(poll_handle.register(device_fd, Flags::READ), "Failed to add device to poll handle: {}"); + if let Err(err) = poll_handle.register(device_fd, Flags::READ) { + if self.device.get_type() != Type::Dummy { + fail!("Failed to add device to poll handle: {}", err); + } else { + warn!("Failed to add device to poll handle: {}", err); + } + } let mut buffer = [0; 64*1024]; let mut poll_error = false; loop { @@ -658,8 +664,10 @@ impl GenericCloud { fd if fd == socket6_fd => try_fail!(self.socket6.recv_from(&mut buffer), "Failed to read from ipv6 network socket: {}"), _ => unreachable!() }; - self.traffic.count_in_traffic(src, size); - if let Err(e) = decode(&mut buffer[..size], self.magic, &mut self.crypto).and_then(|msg| self.handle_net_message(src, msg)) { + if let Err(e) = decode(&mut buffer[..size], self.magic, &mut self.crypto).and_then(|msg| { + self.traffic.count_in_traffic(src, size); + self.handle_net_message(src, msg) + }) { error!("Error: {}, from: {}", e, src); } }, diff --git a/src/config.rs b/src/config.rs index 8de80f0..3c4cb13 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher}; use siphasher::sip::SipHasher24; -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Deserialize, Debug, PartialEq, Clone)] pub struct Config { pub device_type: Type, pub device_name: String, @@ -25,6 +25,7 @@ pub struct Config { pub port: u16, pub peers: Vec, pub peer_timeout: Duration, + pub keepalive: Option, pub mode: Mode, pub dst_timeout: Duration, pub subnets: Vec, @@ -43,7 +44,7 @@ impl Default for Config { ifup: None, ifdown: None, crypto: CryptoMethod::ChaCha20, shared_key: None, magic: None, - port: 3210, peers: vec![], peer_timeout: 1800, + port: 3210, peers: vec![], peer_timeout: 1800, keepalive: None, mode: Mode::Normal, dst_timeout: 300, subnets: vec![], port_forwarding: true, @@ -88,6 +89,9 @@ impl Config { if let Some(val) = file.peer_timeout { self.peer_timeout = val; } + if let Some(val) = file.keepalive { + self.keepalive = Some(val); + } if let Some(val) = file.mode { self.mode = val; } @@ -147,6 +151,9 @@ impl Config { if let Some(val) = args.flag_peer_timeout { self.peer_timeout = val; } + if let Some(val) = args.flag_keepalive { + self.keepalive = Some(val); + } if let Some(val) = args.flag_mode { self.mode = val; } @@ -192,6 +199,13 @@ impl Config { MAGIC } } + + pub fn get_keepalive(&self) -> Duration { + match self.keepalive { + Some(dur) => dur, + None => self.peer_timeout/2-60 + } + } } @@ -207,6 +221,7 @@ pub struct ConfigFile { pub port: Option, pub peers: Option>, pub peer_timeout: Option, + pub keepalive: Option, pub mode: Option, pub dst_timeout: Option, pub subnets: Option>, diff --git a/src/crypto.rs b/src/crypto.rs index c76fe3e..6a8e17a 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -10,7 +10,7 @@ use ring::digest::*; use super::types::Error; -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] pub enum CryptoMethod { #[serde(rename = "chacha20")] ChaCha20, diff --git a/src/device.rs b/src/device.rs index e295e41..3d333f7 100644 --- a/src/device.rs +++ b/src/device.rs @@ -23,7 +23,10 @@ pub enum Type { Tun, /// Tap interface: This insterface transports Ethernet frames. #[serde(rename = "tap")] - Tap + Tap, + /// Dummy interface: This interface does nothing. + #[serde(rename = "dummy")] + Dummy } impl fmt::Display for Type { @@ -31,6 +34,7 @@ impl fmt::Display for Type { match *self { Type::Tun => write!(formatter, "tun"), Type::Tap => write!(formatter, "tap"), + Type::Dummy => write!(formatter, "dummy") } } } @@ -64,6 +68,9 @@ impl Device { /// # Panics /// This method panics if the interface name is longer than 31 bytes. pub fn new(ifname: &str, type_: Type) -> io::Result { + if type_ == Type::Dummy { + return Self::dummy(ifname, "/dev/null", type_); + } let fd = try!(fs::OpenOptions::new().read(true).write(true).open("/dev/net/tun")); // Add trailing \0 to interface name let mut ifname_string = String::with_capacity(32); @@ -73,7 +80,8 @@ impl Device { let mut ifname_c = ifname_string.into_bytes(); let res = match type_ { Type::Tun => unsafe { setup_tun_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) }, - Type::Tap => unsafe { setup_tap_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) } + Type::Tap => unsafe { setup_tap_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) }, + Type::Dummy => unreachable!() }; match res { 0 => { diff --git a/src/main.rs b/src/main.rs index 7cbbf12..1835757 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,6 +78,7 @@ pub struct Args { flag_magic: Option, flag_connect: Vec, flag_peer_timeout: Option, + flag_keepalive: Option, flag_dst_timeout: Option, flag_verbose: bool, flag_quiet: bool, @@ -159,15 +160,15 @@ enum AnyCloud { impl AnyCloud

{ #[allow(unknown_lints,clippy::too_many_arguments)] - fn new(magic: HeaderMagic, device: Device, listen: u16, table: AnyTable, - peer_timeout: Duration, learning: bool, broadcast: bool, addresses: Vec, - crypto: Crypto, port_forwarding: Option, stats_file: Option) -> Self { + fn new(config: &Config, device: Device, table: AnyTable, + learning: bool, broadcast: bool, addresses: Vec, + crypto: Crypto, port_forwarding: Option) -> Self { match table { AnyTable::Switch(t) => AnyCloud::Switch(GenericCloud::::new( - magic, device, listen, t, peer_timeout, learning, broadcast, addresses, crypto, port_forwarding, stats_file + config, device,t, learning, broadcast, addresses, crypto, port_forwarding )), AnyTable::Routing(t) => AnyCloud::Routing(GenericCloud::::new( - magic, device, listen, t, peer_timeout, learning, broadcast, addresses, crypto, port_forwarding, stats_file + config, device,t, learning, broadcast, addresses, crypto, port_forwarding )) } } @@ -211,20 +212,19 @@ fn run (config: Config) { ranges.push(try_fail!(Range::from_str(s), "Invalid subnet format: {} ({})", s)); } let dst_timeout = config.dst_timeout; - let peer_timeout = config.peer_timeout; let (learning, broadcasting, table) = match config.mode { Mode::Normal => match config.device_type { Type::Tap => (true, true, AnyTable::Switch(SwitchTable::new(dst_timeout, 10))), - Type::Tun => (false, false, AnyTable::Routing(RoutingTable::new())) + Type::Tun => (false, false, AnyTable::Routing(RoutingTable::new())), + Type::Dummy => (false, false, AnyTable::Switch(SwitchTable::new(dst_timeout, 10))) }, Mode::Router => (false, false, AnyTable::Routing(RoutingTable::new())), Mode::Switch => (true, true, AnyTable::Switch(SwitchTable::new(dst_timeout, 10))), Mode::Hub => (false, true, AnyTable::Switch(SwitchTable::new(dst_timeout, 10))) }; - let magic = config.get_magic(); Crypto::init(); let crypto = match config.shared_key { - Some(key) => Crypto::from_shared_key(config.crypto, &key), + Some(ref key) => Crypto::from_shared_key(config.crypto, key), None => Crypto::None }; let port_forwarding = if config.port_forwarding { @@ -232,7 +232,7 @@ fn run (config: Config) { } else { None }; - let mut cloud = AnyCloud::

::new(magic, device, config.port, table, peer_timeout, learning, broadcasting, ranges, crypto, port_forwarding, config.stats_file); + let mut cloud = AnyCloud::

::new(&config, device, table, learning,broadcasting,ranges, crypto, port_forwarding); if let Some(script) = config.ifup { run_script(&script, cloud.ifname()); } @@ -295,6 +295,7 @@ fn main() { debug!("Config: {:?}", config); match config.device_type { Type::Tap => run::(config), - Type::Tun => run::(config) + Type::Tun => run::(config), + Type::Dummy => run::(config) } } diff --git a/src/tests.rs b/src/tests.rs index 2397539..ecde278 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -456,6 +456,7 @@ peers: - remote.machine.foo:3210 - remote.machine.bar:3210 peer_timeout: 1800 +keepalive: 840 dst_timeout: 300 mode: normal subnets: @@ -464,6 +465,7 @@ port_forwarding: true user: nobody group: nogroup pid_file: /run/vpncloud.run +stats_file: /var/log/vpncloud.stats "; assert_eq!(serde_yaml::from_str::(config_file).unwrap(), ConfigFile{ device_type: Some(Type::Tun), @@ -476,13 +478,15 @@ pid_file: /run/vpncloud.run port: Some(3210), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peer_timeout: Some(1800), + keepalive: Some(840), mode: Some(Mode::Normal), dst_timeout: Some(300), subnets: Some(vec!["10.0.1.0/24".to_string()]), port_forwarding: Some(true), user: Some("nobody".to_string()), group: Some("nogroup".to_string()), - pid_file: Some("/run/vpncloud.run".to_string()) + pid_file: Some("/run/vpncloud.run".to_string()), + stats_file: Some("/var/log/vpncloud.stats".to_string()) }) } @@ -500,13 +504,15 @@ fn config_merge() { port: Some(3210), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), peer_timeout: Some(1800), + keepalive: Some(840), mode: Some(Mode::Normal), dst_timeout: Some(300), subnets: Some(vec!["10.0.1.0/24".to_string()]), port_forwarding: Some(true), user: Some("nobody".to_string()), group: Some("nogroup".to_string()), - pid_file: Some("/run/vpncloud.run".to_string()) + pid_file: Some("/run/vpncloud.run".to_string()), + stats_file: Some("/var/log/vpncloud.stats".to_string()) }); assert_eq!(config, Config{ device_type: Type::Tun, @@ -519,6 +525,7 @@ fn config_merge() { port: 3210, peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()], peer_timeout: 1800, + keepalive: Some(840), dst_timeout: 300, mode: Mode::Normal, port_forwarding: true, @@ -526,6 +533,7 @@ fn config_merge() { user: Some("nobody".to_string()), group: Some("nogroup".to_string()), pid_file: Some("/run/vpncloud.run".to_string()), + stats_file: Some("/var/log/vpncloud.stats".to_string()), ..Default::default() }); config.merge_args(Args{ @@ -538,6 +546,7 @@ fn config_merge() { flag_magic: Some("hash:mynet".to_string()), flag_listen: Some(3211), flag_peer_timeout: Some(1801), + flag_keepalive: Some(850), flag_dst_timeout: Some(301), flag_mode: Some(Mode::Switch), flag_subnet: vec![], @@ -545,6 +554,7 @@ fn config_merge() { flag_no_port_forwarding: true, flag_daemon: true, flag_pid_file: Some("/run/vpncloud-mynet.run".to_string()), + flag_stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()), flag_user: Some("root".to_string()), flag_group: Some("root".to_string()), ..Default::default() @@ -560,6 +570,7 @@ fn config_merge() { port: 3211, peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string(), "another:3210".to_string()], peer_timeout: 1801, + keepalive: Some(850), dst_timeout: 301, mode: Mode::Switch, port_forwarding: false, @@ -567,6 +578,7 @@ fn config_merge() { user: Some("root".to_string()), group: Some("root".to_string()), pid_file: Some("/run/vpncloud-mynet.run".to_string()), + stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()), daemonize: true, ..Default::default() }); diff --git a/src/usage.txt b/src/usage.txt index a57a07e..c971ebe 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -19,6 +19,8 @@ Options: --crypto The encryption method to use ("aes256", or "chacha20"). --peer-timeout Peer timeout in seconds. + --keepalive Periodically send message to keep + connections alive. --dst-timeout Switch table entry timeout in seconds. --ifup A command to setup the network interface. --ifdown A command to bring down the network