Added support for YAML config file (re #8)

This commit is contained in:
Dennis Schwerdel 2016-08-08 16:30:22 +02:00
parent 14c63de8b4
commit 06fc814b4b
9 changed files with 588 additions and 69 deletions

View File

@ -4,7 +4,10 @@ This project follows [semantic versioning](http://semver.org).
### UNRELEASED
- [added] Added `-s` shorthand for `--subnet`
- [added] Added support for YAML config file via `--config`
- [changed] Configurable magic header is now used instead of Network-ID (**incompatible**)
- [changed] Clarified documentation on TUN netmasks
- [fixed] Fixed documentation of listen parameter
- [fixed] Fixed problem with multiple subnets

7
Cargo.lock generated
View File

@ -16,6 +16,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"signal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -247,6 +248,11 @@ dependencies = [
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "yaml-rust"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9"
"checksum aligned_alloc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e970f9697cd6bb78fa6616c04b36bd0d2e9259846b9ebe10562fbedc4823d896"
@ -280,3 +286,4 @@ dependencies = [
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
"checksum yaml-rust 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ebfe12f475ad59be6178ebf004d51e682022496535994f8d23fd7ed31084598c"

View File

@ -23,6 +23,7 @@ rand = "0.3"
fnv = "1"
net2 = "0.2"
bitflags = "0.7"
yaml-rust = "0.3"
[build-dependencies]
gcc = "0.3"

View File

@ -34,6 +34,7 @@ somewhat stable state. VpnCloud features the following functionality:
Ethernet networks.
* High throughput and low additional latency (see [performance page](https://github.com/dswd/vpncloud.rs/wiki/Performance-Measurements))
* Support for tunneled VLans (TAP device)
* Option to hide protocol header
### Installing

163
src/config.rs Normal file
View File

@ -0,0 +1,163 @@
use super::{MAGIC, Args};
use device::Type;
use types::{Mode, HeaderMagic};
use crypto::CryptoMethod;
use util::{Encoder, Duration};
use std::hash::{Hash, SipHasher, Hasher};
#[derive(RustcDecodable, Debug)]
pub struct Config {
pub device_type: Type,
pub device_name: String,
pub ifup: Option<String>,
pub ifdown: Option<String>,
pub crypto: CryptoMethod,
pub shared_key: Option<String>,
pub magic: Option<String>,
pub port: u16,
pub peers: Vec<String>,
pub peer_timeout: Duration,
pub mode: Mode,
pub dst_timeout: Duration,
pub subnets: Vec<String>,
}
impl Default for Config {
fn default() -> Self {
Config {
device_type: Type::Tap, device_name: "vpncloud%d".to_string(),
ifup: None, ifdown: None,
crypto: CryptoMethod::ChaCha20, shared_key: None,
magic: None,
port: 3210, peers: vec![], peer_timeout: 1800,
mode: Mode::Normal, dst_timeout: 300,
subnets: vec![]
}
}
}
impl Config {
pub fn merge_file(&mut self, file: ConfigFile) {
if let Some(val) = file.device_type {
self.device_type = val;
}
if let Some(val) = file.device_name {
self.device_name = val;
}
if let Some(val) = file.ifup {
self.ifup = Some(val);
}
if let Some(val) = file.ifdown {
self.ifdown = Some(val);
}
if let Some(val) = file.crypto {
self.crypto = val;
}
if let Some(val) = file.shared_key {
self.shared_key = Some(val);
}
if let Some(val) = file.magic {
self.magic = Some(val);
}
if let Some(val) = file.port {
self.port = val;
}
if let Some(mut val) = file.peers {
self.peers.append(&mut val);
}
if let Some(val) = file.peer_timeout {
self.peer_timeout = val;
}
if let Some(val) = file.mode {
self.mode = val;
}
if let Some(val) = file.dst_timeout {
self.dst_timeout = val;
}
if let Some(mut val) = file.subnets {
self.subnets.append(&mut val);
}
}
pub fn merge_args(&mut self, mut args: Args) {
if let Some(val) = args.flag_type {
self.device_type = val;
}
if let Some(val) = args.flag_device {
self.device_name = val;
}
if let Some(val) = args.flag_ifup {
self.ifup = Some(val);
}
if let Some(val) = args.flag_ifdown {
self.ifdown = Some(val);
}
if let Some(val) = args.flag_crypto {
self.crypto = val;
}
if let Some(val) = args.flag_shared_key {
self.shared_key = Some(val);
}
if let Some(val) = args.flag_network_id {
warn!("The --network-id argument is deprecated, please use --magic instead.");
self.magic = Some(val);
}
if let Some(val) = args.flag_magic {
self.magic = Some(val);
}
if let Some(val) = args.flag_listen {
self.port = val;
}
self.peers.append(&mut args.flag_connect);
if let Some(val) = args.flag_peer_timeout {
self.peer_timeout = val;
}
if let Some(val) = args.flag_mode {
self.mode = val;
}
if let Some(val) = args.flag_dst_timeout {
self.dst_timeout = val;
}
self.subnets.append(&mut args.flag_subnet);
}
pub fn get_magic(&self) -> HeaderMagic {
if let Some(ref name) = self.magic {
if name.starts_with("hash:") {
let mut s = SipHasher::new();
name[6..].hash(&mut s);
let mut data = [0; 4];
Encoder::write_u32((s.finish() & 0xffffffff) as u32, &mut data);
data
} else {
let num = try_fail!(u32::from_str_radix(&name, 16), "Failed to parse header magic: {}");
let mut data = [0; 4];
Encoder::write_u32(num, &mut data);
data
}
} else {
MAGIC
}
}
}
#[derive(RustcDecodable, Debug)]
pub struct ConfigFile {
pub device_type: Option<Type>,
pub device_name: Option<String>,
pub ifup: Option<String>,
pub ifdown: Option<String>,
pub crypto: Option<CryptoMethod>,
pub shared_key: Option<String>,
pub magic: Option<String>,
pub port: Option<u16>,
pub peers: Option<Vec<String>>,
pub peer_timeout: Option<Duration>,
pub mode: Option<Mode>,
pub dst_timeout: Option<Duration>,
pub subnets: Option<Vec<String>>,
}

306
src/configfile.rs Normal file
View File

@ -0,0 +1,306 @@
extern crate yaml_rust;
extern crate rustc_serialize;
use std::fs::File;
use std::io::{self, Read};
use std::ops::Deref;
use std::ascii::AsciiExt;
use std::fmt;
use rustc_serialize::{Decodable, Decoder};
use yaml_rust::{yaml, Yaml, YamlLoader, ScanError};
#[derive(Debug)]
pub enum ParseError {
Read(io::Error),
Syntax(ScanError),
NoDocument,
InvalidType(&'static str, Yaml),
InvalidOption(String, Vec<String>),
Unsupported(&'static str),
Other(String)
}
impl fmt::Display for ParseError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ParseError::Read(ref err) => write!(formatter, "Failed to read config: {}", err),
ParseError::Syntax(ref err) => write!(formatter, "Invalid config syntax: {}", err),
ParseError::NoDocument => write!(formatter, "Config was empty"),
ParseError::InvalidType(ref exp, ref value) => write!(formatter, "Invalid value encountered for type {}: {:?}", exp, value),
ParseError::InvalidOption(ref opt, ref opts) => write!(formatter, "Invalid option value {}, choices were {:?}", opt, opts),
ParseError::Unsupported(ref reason) => write!(formatter, "Failed to decode config: {}", reason),
ParseError::Other(ref reason) => write!(formatter, "Failed to decode config: {}", reason)
}
}
}
pub fn parse<T: Decodable>(file: &str) -> Result<T, ParseError> {
let mut file = try!(File::open(file).map_err(ParseError::Read));
let mut text = String::new();
try!(file.read_to_string(&mut text).map_err(ParseError::Read));
parse_str(&text)
}
pub fn parse_str<T: Decodable>(text: &str) -> Result<T, ParseError> {
if let Some(yaml) = try!(YamlLoader::load_from_str(text).map_err(ParseError::Syntax)).pop() {
parse_yaml(yaml)
} else {
Err(ParseError::NoDocument)
}
}
pub fn parse_yaml<T: Decodable>(yaml: Yaml) -> Result<T, ParseError> {
T::decode(&mut YamlDecoder(yaml))
}
struct YamlDecoder(Yaml);
static NULL: Yaml = Yaml::Null;
impl YamlDecoder {
#[inline]
fn get_field(&self, name: &str) -> Result<Yaml, ParseError> {
match self.as_hash() {
Some(hash) => match hash.get(&Yaml::String(name.to_string())) {
Some(field) => Ok(field.clone()),
None => Ok(Yaml::Null)
},
None => Err(ParseError::InvalidType("hash", self.0.clone()))
}
}
#[inline]
fn get_item(&self, index: usize) -> Result<&Yaml, ParseError> {
match self.as_vec() {
Some(vec) => if vec.len() > index {
Ok(&vec[index])
} else {
Ok(&NULL)
},
None => Err(ParseError::InvalidType("hash", self.0.clone()))
}
}
#[inline]
fn vec(&self) -> Result<&yaml::Array, ParseError> {
match self.as_vec() {
Some(vec) => Ok(vec),
None => Err(ParseError::InvalidType("list", self.0.clone()))
}
}
#[inline]
fn hash(&self) -> Result<&yaml::Hash, ParseError> {
match self.as_hash() {
Some(hash) => Ok(hash),
None => Err(ParseError::InvalidType("hash", self.0.clone()))
}
}
#[inline]
fn bool(&self) -> Result<bool, ParseError> {
self.0.as_bool().ok_or_else(|| ParseError::InvalidType("bool", self.0.clone()))
}
#[inline]
fn num(&self) -> Result<i64, ParseError> {
self.0.as_i64().ok_or_else(|| ParseError::InvalidType("number", self.0.clone()))
}
#[inline]
fn float(&self) -> Result<f64, ParseError> {
self.0.as_f64().ok_or_else(|| ParseError::InvalidType("float", self.0.clone()))
}
#[inline]
fn str(&self) -> Result<&str, ParseError> {
self.as_str().ok_or_else(|| ParseError::InvalidType("string", self.0.clone()))
}
}
impl Deref for YamlDecoder {
type Target = Yaml;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Decoder for YamlDecoder {
type Error = ParseError;
#[inline]
fn read_nil(&mut self) -> Result<(), ParseError> {
Ok(())
}
#[inline]
fn read_bool(&mut self) -> Result<bool, ParseError> {
self.bool()
}
#[inline]
fn read_f64(&mut self) -> Result<f64, ParseError> {
self.float()
}
#[inline]
fn read_f32(&mut self) -> Result<f32, ParseError> {
Ok(try!(self.float()) as f32)
}
#[inline]
fn read_i64(&mut self) -> Result<i64, ParseError> {
self.num()
}
#[inline]
fn read_i32(&mut self) -> Result<i32, ParseError> {
Ok(try!(self.num()) as i32)
}
#[inline]
fn read_i16(&mut self) -> Result<i16, ParseError> {
Ok(try!(self.num()) as i16)
}
#[inline]
fn read_i8(&mut self) -> Result<i8, ParseError> {
Ok(try!(self.num()) as i8)
}
#[inline]
fn read_isize(&mut self) -> Result<isize, ParseError> {
Ok(try!(self.num()) as isize)
}
#[inline]
fn read_u64(&mut self) -> Result<u64, ParseError> {
Ok(try!(self.num()) as u64)
}
#[inline]
fn read_u32(&mut self) -> Result<u32, ParseError> {
Ok(try!(self.read_u64()) as u32)
}
#[inline]
fn read_u16(&mut self) -> Result<u16, ParseError> {
Ok(try!(self.read_u64()) as u16)
}
#[inline]
fn read_u8(&mut self) -> Result<u8, ParseError> {
Ok(try!(self.read_u64()) as u8)
}
#[inline]
fn read_usize(&mut self) -> Result<usize, ParseError> {
Ok(try!(self.read_u64()) as usize)
}
#[inline]
fn read_str(&mut self) -> Result<String, ParseError> {
self.str().map(|val| val.to_string())
}
#[inline]
fn read_char(&mut self) -> Result<char, ParseError> {
let string = try!(self.str());
if string.len() == 1 {
Ok(string.chars().next().unwrap())
} else {
Err(ParseError::InvalidType("char", self.clone()))
}
}
fn read_enum<T, F>(&mut self, _name: &str, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(self)
}
fn read_enum_variant<T, F>(&mut self, names: &[&str], mut f: F) -> Result<T, Self::Error> where F: FnMut(&mut Self, usize) -> Result<T, Self::Error> {
let name = try!(self.read_str());
for (i, n) in names.iter().enumerate() {
if n == &name {
return f(self, i)
}
}
for (i, n) in names.iter().enumerate() {
if n.eq_ignore_ascii_case(&name) {
return f(self, i)
}
}
Err(ParseError::InvalidOption(name, names.iter().map(|s| s.to_string()).collect()))
}
fn read_enum_variant_arg<T, F>(&mut self, _a_idx: usize, _f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
Err(ParseError::Unsupported("Enum variants with aruments are not supported"))
}
fn read_enum_struct_variant<T, F>(&mut self, _names: &[&str], _f: F) -> Result<T, Self::Error> where F: FnMut(&mut Self, usize) -> Result<T, Self::Error> {
Err(ParseError::Unsupported("Enum variants with aruments are not supported"))
}
fn read_enum_struct_variant_field<T, F>(&mut self, _f_name: &str, _f_idx: usize, _f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
Err(ParseError::Unsupported("Enum variants with aruments are not supported"))
}
fn read_struct<T, F>(&mut self, _s_name: &str, _len: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(self)
}
fn read_struct_field<T, F>(&mut self, f_name: &str, _f_idx: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(&mut YamlDecoder(try!(self.get_field(f_name))))
}
fn read_tuple<T, F>(&mut self, _len: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(self)
}
fn read_tuple_arg<T, F>(&mut self, a_idx: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(&mut YamlDecoder(try!(self.get_item(a_idx)).clone()))
}
fn read_tuple_struct<T, F>(&mut self, _s_name: &str, _len: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(self)
}
fn read_tuple_struct_arg<T, F>(&mut self, a_idx: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(&mut YamlDecoder(try!(self.get_item(a_idx)).clone()))
}
fn read_option<T, F>(&mut self, mut f: F) -> Result<T, Self::Error> where F: FnMut(&mut Self, bool) -> Result<T, Self::Error> {
let isset = !self.is_null();
f(self, isset)
}
fn read_seq<T, F>(&mut self, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self, usize) -> Result<T, Self::Error> {
let len = try!(self.vec()).len();
f(self, len)
}
fn read_seq_elt<T, F>(&mut self, idx: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(&mut YamlDecoder(try!(self.vec())[idx].clone()))
}
fn read_map<T, F>(&mut self, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self, usize) -> Result<T, Self::Error> {
let len = try!(self.hash()).len();
f(self, len)
}
fn read_map_elt_key<T, F>(&mut self, idx: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(&mut YamlDecoder(try!(self.hash()).into_iter().nth(idx).unwrap().0.clone()))
}
fn read_map_elt_val<T, F>(&mut self, idx: usize, f: F) -> Result<T, Self::Error> where F: FnOnce(&mut Self) -> Result<T, Self::Error> {
f(&mut YamlDecoder(try!(self.hash()).into_iter().nth(idx).unwrap().1.clone()))
}
fn error(&mut self, err: &str) -> Self::Error {
ParseError::Other(err.to_string())
}
}

View File

@ -16,6 +16,7 @@ extern crate aligned_alloc;
extern crate rand;
extern crate fnv;
extern crate net2;
extern crate yaml_rust;
#[cfg(feature = "bench")] extern crate test;
#[macro_use] pub mod util;
@ -27,12 +28,13 @@ pub mod ip;
pub mod cloud;
pub mod device;
pub mod poll;
pub mod config;
pub mod configfile;
#[cfg(test)] mod tests;
#[cfg(feature = "bench")] mod benches;
use docopt::Docopt;
use std::hash::{Hash, SipHasher, Hasher};
use std::str::FromStr;
use std::process::Command;
@ -42,12 +44,37 @@ use ip::RoutingTable;
use types::{Mode, Range, Table, Protocol, HeaderMagic};
use cloud::GenericCloud;
use crypto::{Crypto, CryptoMethod};
use util::{Duration, Encoder};
use util::Duration;
use config::Config;
const VERSION: u8 = 1;
const MAGIC: HeaderMagic = *b"vpn\x01";
static USAGE: &'static str = include_str!("usage.txt");
#[derive(RustcDecodable, Debug)]
pub struct Args {
flag_config: Option<String>,
flag_type: Option<Type>,
flag_mode: Option<Mode>,
flag_shared_key: Option<String>,
flag_crypto: Option<CryptoMethod>,
flag_subnet: Vec<String>,
flag_device: Option<String>,
flag_listen: Option<u16>,
flag_network_id: Option<String>,
flag_magic: Option<String>,
flag_connect: Vec<String>,
flag_peer_timeout: Option<Duration>,
flag_dst_timeout: Option<Duration>,
flag_verbose: bool,
flag_quiet: bool,
flag_ifup: Option<String>,
flag_ifdown: Option<String>,
flag_version: bool
}
struct SimpleLogger;
@ -65,29 +92,6 @@ impl log::Log for SimpleLogger {
}
}
static USAGE: &'static str = include_str!("usage.txt");
#[derive(RustcDecodable, Debug)]
struct Args {
flag_type: Type,
flag_mode: Mode,
flag_shared_key: Option<String>,
flag_crypto: CryptoMethod,
flag_subnet: Vec<String>,
flag_device: String,
flag_listen: u16,
flag_network_id: Option<String>,
flag_magic: Option<String>,
flag_connect: Vec<String>,
flag_peer_timeout: Duration,
flag_dst_timeout: Duration,
flag_verbose: bool,
flag_quiet: bool,
flag_ifup: Option<String>,
flag_ifdown: Option<String>,
flag_version: bool
}
fn run_script(script: String, ifname: &str) {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
@ -102,18 +106,18 @@ fn run_script(script: String, ifname: &str) {
}
}
fn run<T: Protocol> (mut args: Args) {
let device = try_fail!(Device::new(&args.flag_device, args.flag_type),
"Failed to open virtual {} interface {}: {}", args.flag_type, &args.flag_device);
fn run<T: Protocol> (config: Config) {
let device = try_fail!(Device::new(&config.device_name, config.device_type),
"Failed to open virtual {} interface {}: {}", config.device_type, config.device_name);
info!("Opened device {}", device.ifname());
let mut ranges = Vec::with_capacity(args.flag_subnet.len());
for s in args.flag_subnet {
ranges.push(try_fail!(Range::from_str(&s), "Invalid subnet format: {} ({})", s));
let mut ranges = Vec::with_capacity(config.subnets.len());
for s in &config.subnets {
ranges.push(try_fail!(Range::from_str(s), "Invalid subnet format: {} ({})", s));
}
let dst_timeout = args.flag_dst_timeout;
let peer_timeout = args.flag_peer_timeout;
let (learning, broadcasting, table): (bool, bool, Box<Table>) = match args.flag_mode {
Mode::Normal => match args.flag_type {
let dst_timeout = config.dst_timeout;
let peer_timeout = config.peer_timeout;
let (learning, broadcasting, table): (bool, bool, Box<Table>) = match config.mode {
Mode::Normal => match config.device_type {
Type::Tap => (true, true, Box::new(SwitchTable::new(dst_timeout))),
Type::Tun => (false, false, Box::new(RoutingTable::new()))
},
@ -121,41 +125,22 @@ fn run<T: Protocol> (mut args: Args) {
Mode::Switch => (true, true, Box::new(SwitchTable::new(dst_timeout))),
Mode::Hub => (false, true, Box::new(SwitchTable::new(dst_timeout)))
};
if let Some(network_id) = args.flag_network_id {
warn!("The --network-id argument is deprecated, please use --magic instead.");
if args.flag_magic.is_none() {
args.flag_magic = Some(network_id);
}
}
let magic = args.flag_magic.map_or(MAGIC, |name| {
if name.starts_with("hash:") {
let mut s = SipHasher::new();
name[6..].hash(&mut s);
let mut data = [0; 4];
Encoder::write_u32((s.finish() & 0xffffffff) as u32, &mut data);
data
} else {
let num = try_fail!(u32::from_str_radix(&name, 16), "Failed to parse header magic: {}");
let mut data = [0; 4];
Encoder::write_u32(num, &mut data);
data
}
});
let magic = config.get_magic();
Crypto::init();
let crypto = match args.flag_shared_key {
Some(key) => Crypto::from_shared_key(args.flag_crypto, &key),
let crypto = match config.shared_key {
Some(key) => Crypto::from_shared_key(config.crypto, &key),
None => Crypto::None
};
let mut cloud = GenericCloud::<T>::new(magic, device, args.flag_listen, table, peer_timeout, learning, broadcasting, ranges, crypto);
if let Some(script) = args.flag_ifup {
let mut cloud = GenericCloud::<T>::new(magic, device, config.port, table, peer_timeout, learning, broadcasting, ranges, crypto);
if let Some(script) = config.ifup {
run_script(script, cloud.ifname());
}
for addr in args.flag_connect {
for addr in config.peers {
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
cloud.add_reconnect_peer(addr);
}
cloud.run();
if let Some(script) = args.flag_ifdown {
if let Some(script) = config.ifdown {
run_script(script, cloud.ifname());
}
}
@ -183,9 +168,16 @@ fn main() {
}
Box::new(SimpleLogger)
}).unwrap();
debug!("Args: {:?}", args);
match args.flag_type {
Type::Tap => run::<ethernet::Frame>(args),
Type::Tun => run::<ip::Packet>(args),
let mut config = Config::default();
if let Some(ref file) = args.flag_config {
info!("Reading config file '{}'", file);
let config_file = try_fail!(configfile::parse(file), "Failed to load config file: {:?}");
config.merge_file(config_file)
}
config.merge_args(args);
debug!("Config: {:?}", config);
match config.device_type {
Type::Tap => run::<ethernet::Frame>(config),
Type::Tun => run::<ip::Packet>(config)
}
}

View File

@ -1,7 +1,9 @@
Usage:
vpncloud [options] [-t <type>] [-d <name>] [-l <listen>] [-c <addr>...] [-s <subnet>...]
vpncloud [options] [--config <file>] [-t <type>] [-d <name>] [-l <listen>] [-c <addr>...] [-s <subnet>...]
Options:
--config <file> Read configuration options from the
specified file.
-t <type>, --type <type> Set the type of network ("tap" or "tun").
[default: tap]
-d <name>, --device <name> Name of the virtual device.
@ -19,9 +21,8 @@ Options:
--shared-key <key> The shared key to encrypt all traffic.
--crypto <method> The encryption method to use ("aes256", or
"chacha20"). [default: chacha20]
--peer-timeout <secs> Peer timeout in seconds. [default: 1800]
--peer-timeout <secs> Peer timeout in seconds.
--dst-timeout <secs> Switch table entry timeout in seconds.
[default: 300]
--ifup <command> A command to setup the network interface.
--ifdown <command> A command to bring down the network
interface.

View File

@ -3,11 +3,18 @@ vpncloud(1) -- Peer-to-peer VPN
## SYNOPSIS
`vpncloud [options] [-t <type>] [-d <name>] [-l <addr>] [-c <addr>...]`
`vpncloud [options] [--config <file>] [-t <type>] [-d <name>] [-l <addr>] [-c <addr>...]`
## OPTIONS
* `--config <file>`:
Read configuration options from the specified file. Please see the section
**CONFIG FILES** for documentation on the file format.
If the same option is defined in the config file and as a parameter, the
parameter overrides the config file.
* `-t <type>`, `--type <type>`:
Set the type of network. There are two options: **tap** devices process
@ -213,6 +220,44 @@ interface is configured with (/16 in this example).
messages, reorder them, or duplicate them).
## CONFIG FILES
The config file is a YAML file that contains configuration values. All entries
are optional and override the defaults. Please see the section **OPTIONS** for
detailed descriptions of the options.
* `device_type`: Set the type of network. Same as `--type`
* `device_name`: Name of the virtual device. Same as `--device`
* `ifup`: A command to setup the network interface. Same as `--ifup`
* `ifup`: A command to bring down the network interface. Same as `--ifdown`
* `crypto`: The encryption method to use. Same as `--crypto`
* `shared_key`: The shared key to encrypt all traffic. Same as `--shared-key`
* `magic`: Override the 4-byte magic header of each packet. Same as `--magic`
* `port`: The port number on which to listen for data. Same as `--listen`
* `peers`: A list of addresses to connect to. See `--connect`
* `peer_timeout`: Peer timeout in seconds. Same as`--peer-timeout`
* `mode`: The mode of the VPN. Same as `--mode`
* `dst_timeout`: Switch table entry timeout in seconds. Same as `--dst-timeout`
* `subnets`: A list of local subnets to use. See `--subnet`
### Example
device_type: tun
device_name: vpncloud%d
ifup: ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up
crypto: aes256
shared_key: mysecret
port: 3210
peers:
- remote.machine.foo:3210
- remote.machine.bar:3210
peer_timeout: 1800
mode: normal
subnets:
- 10.0.1.0/24
## NETWORK PROTOCOL
The protocol of VpnCloud is kept as simple as possible to allow other