Many changes for 0.9.0

pull/29/head
Dennis Schwerdel 2019-01-10 19:36:50 +01:00
parent fe25ecec1d
commit fecffcce95
10 changed files with 90 additions and 39 deletions

View File

@ -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

2
Cargo.lock generated
View File

@ -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)",

View File

@ -1,7 +1,7 @@
[package]
name = "vpncloud"
version = "0.8.2"
authors = ["Dennis Schwerdel <schwerdel@informatik.uni-kl.de>"]
version = "0.9.0"
authors = ["Dennis Schwerdel <schwerdel@googlemail.com>"]
build = "build.rs"
license = "GPL-3.0"
description = "Peer-to-peer VPN"

View File

@ -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<P: Protocol, T: Table> {
}
impl<P: Protocol, T: Table> GenericCloud<P, T> {
#[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<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding>, stats_file: Option<String>
pub fn new(config: &Config, device: Device, table: T,
learning: bool, broadcast: bool, addresses: Vec<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding>
) -> 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<P: Protocol, T: Table> GenericCloud<P, T> {
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<P: Protocol, T: Table> GenericCloud<P, T> {
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<P: Protocol, T: Table> GenericCloud<P, T> {
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);
}
},

View File

@ -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<String>,
pub peer_timeout: Duration,
pub keepalive: Option<Duration>,
pub mode: Mode,
pub dst_timeout: Duration,
pub subnets: Vec<String>,
@ -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<u16>,
pub peers: Option<Vec<String>>,
pub peer_timeout: Option<Duration>,
pub keepalive: Option<Duration>,
pub mode: Option<Mode>,
pub dst_timeout: Option<Duration>,
pub subnets: Option<Vec<String>>,

View File

@ -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,

View File

@ -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<Self> {
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 => {

View File

@ -78,6 +78,7 @@ pub struct Args {
flag_magic: Option<String>,
flag_connect: Vec<String>,
flag_peer_timeout: Option<Duration>,
flag_keepalive: Option<Duration>,
flag_dst_timeout: Option<Duration>,
flag_verbose: bool,
flag_quiet: bool,
@ -159,15 +160,15 @@ enum AnyCloud<P: Protocol> {
impl<P: Protocol> AnyCloud<P> {
#[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<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding>, stats_file: Option<String>) -> Self {
fn new(config: &Config, device: Device, table: AnyTable,
learning: bool, broadcast: bool, addresses: Vec<Range>,
crypto: Crypto, port_forwarding: Option<PortForwarding>) -> Self {
match table {
AnyTable::Switch(t) => AnyCloud::Switch(GenericCloud::<P, SwitchTable>::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::<P, RoutingTable>::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<P: Protocol> (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<P: Protocol> (config: Config) {
} else {
None
};
let mut cloud = AnyCloud::<P>::new(magic, device, config.port, table, peer_timeout, learning, broadcasting, ranges, crypto, port_forwarding, config.stats_file);
let mut cloud = AnyCloud::<P>::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::<ethernet::Frame>(config),
Type::Tun => run::<ip::Packet>(config)
Type::Tun => run::<ip::Packet>(config),
Type::Dummy => run::<ethernet::Frame>(config)
}
}

View File

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

View File

@ -19,6 +19,8 @@ Options:
--crypto <method> The encryption method to use ("aes256", or
"chacha20").
--peer-timeout <secs> Peer timeout in seconds.
--keepalive <secs> Periodically send message to keep
connections alive.
--dst-timeout <secs> Switch table entry timeout in seconds.
--ifup <command> A command to setup the network interface.
--ifdown <command> A command to bring down the network