mirror of https://github.com/dswd/vpncloud.git
Added support for YAML config file (re #8)
This commit is contained in:
parent
14c63de8b4
commit
06fc814b4b
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -23,6 +23,7 @@ rand = "0.3"
|
|||
fnv = "1"
|
||||
net2 = "0.2"
|
||||
bitflags = "0.7"
|
||||
yaml-rust = "0.3"
|
||||
|
||||
[build-dependencies]
|
||||
gcc = "0.3"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>,
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
122
src/main.rs
122
src/main.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
47
vpncloud.md
47
vpncloud.md
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue