Client works

This commit is contained in:
Dennis Schwerdel 2020-12-20 01:40:32 +01:00
parent edd0e7a29f
commit d50490ac51
7 changed files with 599 additions and 201 deletions

241
Cargo.lock generated
View File

@ -31,18 +31,39 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base-x"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "boxfnonce"
version = "0.1.1"
@ -106,6 +127,28 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "daemonize"
version = "0.4.1"
@ -116,6 +159,15 @@ dependencies = [
"libc",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "discard"
version = "1.0.4"
@ -134,6 +186,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.0"
@ -144,6 +211,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.15"
@ -195,6 +272,12 @@ dependencies = [
"itoa",
]
[[package]]
name = "httparse"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
[[package]]
name = "idna"
version = "0.2.0"
@ -219,6 +302,15 @@ dependencies = [
"xmltree",
]
[[package]]
name = "input_buffer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
dependencies = [
"bytes",
]
[[package]]
name = "itoa"
version = "0.4.6"
@ -267,6 +359,24 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "native-tls"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nix"
version = "0.14.1"
@ -298,12 +408,57 @@ version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d008f51b1acffa0d3450a68606e6a51c123012edaacb0f4e1426bd978869187"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"foreign-types",
"lazy_static",
"libc",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]]
name = "openssl-sys"
version = "0.9.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pkg-config"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@ -494,6 +649,39 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "security-framework"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -549,6 +737,19 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "sha-1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpuid-bool",
"digest",
"opaque-debug",
]
[[package]]
name = "sha1"
version = "0.6.0"
@ -772,6 +973,32 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tungstenite"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23"
dependencies = [
"base64",
"byteorder",
"bytes",
"http",
"httparse",
"input_buffer",
"log",
"native-tls",
"rand 0.7.3",
"sha-1",
"url",
"utf-8",
]
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicode-bidi"
version = "0.3.4"
@ -826,6 +1053,18 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]]
name = "vcpkg"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
[[package]]
name = "vec_map"
version = "0.8.2"
@ -866,6 +1105,8 @@ dependencies = [
"tempfile",
"thiserror",
"time",
"tungstenite",
"url",
"yaml-rust",
]

View File

@ -30,6 +30,8 @@ privdrop = "0.5"
byteorder = "1.3"
thiserror = "1.0"
smallvec = "1.5"
tungstenite = "*"
url = "*"
[dev-dependencies]
tempfile = "3"

View File

@ -98,11 +98,7 @@ pub struct GenericCloud<D: Device, P: Protocol, S: Socket, TS: TimeSource> {
impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS> {
#[allow(clippy::too_many_arguments)]
pub fn new(config: &Config, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self {
let socket = match S::listen(config.listen) {
Ok(socket) => socket,
Err(err) => fail!("Failed to open socket {}: {}", config.listen, err)
};
pub fn new(config: &Config, socket: S, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self {
let (learning, broadcast) = match config.mode {
Mode::Normal => {
match config.device_type {

View File

@ -5,29 +5,12 @@
use super::{device::Type, types::Mode, util::Duration};
pub use crate::crypto::Config as CryptoConfig;
use std::{
cmp::max,
net::{IpAddr, Ipv6Addr, SocketAddr}
};
use std::cmp::max;
use structopt::StructOpt;
pub const DEFAULT_PEER_TIMEOUT: u16 = 300;
pub const DEFAULT_PORT: u16 = 3210;
fn parse_listen(addr: &str) -> SocketAddr {
if let Some(addr) = addr.strip_prefix("*:") {
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
} else if addr.contains(':') {
try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr)
} else {
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
}
}
#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct Config {
pub device_type: Type,
@ -41,7 +24,7 @@ pub struct Config {
pub crypto: CryptoConfig,
pub listen: SocketAddr,
pub listen: String,
pub peers: Vec<String>,
pub peer_timeout: Duration,
pub keepalive: Option<Duration>,
@ -60,7 +43,7 @@ pub struct Config {
pub statsd_server: Option<String>,
pub statsd_prefix: Option<String>,
pub user: Option<String>,
pub group: Option<String>
pub group: Option<String>,
}
impl Default for Config {
@ -74,7 +57,7 @@ impl Default for Config {
ifup: None,
ifdown: None,
crypto: CryptoConfig::default(),
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
listen: "[::]:3210".to_string(),
peers: vec![],
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
keepalive: None,
@ -93,7 +76,7 @@ impl Default for Config {
statsd_server: None,
statsd_prefix: None,
user: None,
group: None
group: None,
}
}
}
@ -125,7 +108,7 @@ impl Config {
self.ifdown = Some(val);
}
if let Some(val) = file.listen {
self.listen = parse_listen(&val);
self.listen = val;
}
if let Some(mut val) = file.peers {
self.peers.append(&mut val);
@ -223,7 +206,7 @@ impl Config {
self.ifdown = Some(val);
}
if let Some(val) = args.listen {
self.listen = parse_listen(&val);
self.listen = val;
}
self.peers.append(&mut args.peers);
if let Some(val) = args.peer_timeout {
@ -296,12 +279,11 @@ impl Config {
pub fn get_keepalive(&self) -> Duration {
match self.keepalive {
Some(dur) => dur,
None => max(self.peer_timeout / 2 - 60, 1)
None => max(self.peer_timeout / 2 - 60, 1),
}
}
}
#[derive(StructOpt, Debug, Default)]
pub struct Args {
/// Read configuration options from the specified file.
@ -325,7 +307,7 @@ pub struct Args {
pub mode: Option<Mode>,
/// The shared password to encrypt all traffic
#[structopt(short, long, required_unless_one = &["private-key", "config", "genkey", "version"], env)]
#[structopt(short, long, env, global = true)]
pub password: Option<String>,
/// The private key to use
@ -416,10 +398,6 @@ pub struct Args {
#[structopt(long)]
pub version: bool,
/// Generate and print a key-pair and exit
#[structopt(long, conflicts_with = "private_key")]
pub genkey: bool,
/// Disable automatic port forwarding
#[structopt(long)]
pub no_port_forwarding: bool,
@ -456,9 +434,28 @@ pub struct Args {
#[structopt(long)]
pub log_file: Option<String>,
/// Migrate an old config file
#[structopt(long, alias = "migrate", requires = "config")]
pub migrate_config: bool
#[structopt(subcommand)]
pub cmd: Option<Command>,
}
#[derive(StructOpt, Debug)]
pub enum Command {
/// Generate and print a key-pair and exit
#[structopt(name = "genkey", alias = "gen-key")]
GenKey,
WsProxy,
WsClient {
url: String,
},
#[structopt(alias = "migrate")]
MigrateConfig {
/// Config file
#[structopt(long)]
config_file: String,
},
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -468,7 +465,7 @@ pub struct ConfigFileDevice {
pub type_: Option<Type>,
pub name: Option<String>,
pub path: Option<String>,
pub fix_rp_filter: Option<bool>
pub fix_rp_filter: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -477,14 +474,14 @@ pub struct ConfigFileBeacon {
pub store: Option<String>,
pub load: Option<String>,
pub interval: Option<Duration>,
pub password: Option<String>
pub password: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
pub struct ConfigFileStatsd {
pub server: Option<String>,
pub prefix: Option<String>
pub prefix: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -512,10 +509,9 @@ pub struct ConfigFile {
pub stats_file: Option<String>,
pub statsd: Option<ConfigFileStatsd>,
pub user: Option<String>,
pub group: Option<String>
pub group: Option<String>,
}
#[test]
fn config_file() {
let config_file = "
@ -549,41 +545,44 @@ statsd:
server: example.com:1234
prefix: prefix
";
assert_eq!(serde_yaml::from_str::<ConfigFile>(config_file).unwrap(), ConfigFile {
device: Some(ConfigFileDevice {
type_: Some(Type::Tun),
name: Some("vpncloud%d".to_string()),
path: Some("/dev/net/tun".to_string()),
fix_rp_filter: None
}),
ip: Some("10.0.1.1/16".to_string()),
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
crypto: CryptoConfig::default(),
listen: None,
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
peer_timeout: Some(600),
keepalive: Some(840),
beacon: Some(ConfigFileBeacon {
store: Some("/run/vpncloud.beacon.out".to_string()),
load: Some("/run/vpncloud.beacon.in".to_string()),
interval: Some(3600),
password: Some("test123".to_string())
}),
mode: Some(Mode::Normal),
switch_timeout: Some(300),
claims: Some(vec!["10.0.1.0/24".to_string()]),
auto_claim: None,
port_forwarding: Some(true),
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()),
statsd: Some(ConfigFileStatsd {
server: Some("example.com:1234".to_string()),
prefix: Some("prefix".to_string())
})
})
assert_eq!(
serde_yaml::from_str::<ConfigFile>(config_file).unwrap(),
ConfigFile {
device: Some(ConfigFileDevice {
type_: Some(Type::Tun),
name: Some("vpncloud%d".to_string()),
path: Some("/dev/net/tun".to_string()),
fix_rp_filter: None
}),
ip: Some("10.0.1.1/16".to_string()),
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
crypto: CryptoConfig::default(),
listen: None,
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
peer_timeout: Some(600),
keepalive: Some(840),
beacon: Some(ConfigFileBeacon {
store: Some("/run/vpncloud.beacon.out".to_string()),
load: Some("/run/vpncloud.beacon.in".to_string()),
interval: Some(3600),
password: Some("test123".to_string())
}),
mode: Some(Mode::Normal),
switch_timeout: Some(300),
claims: Some(vec!["10.0.1.0/24".to_string()]),
auto_claim: None,
port_forwarding: Some(true),
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()),
statsd: Some(ConfigFileStatsd {
server: Some("example.com:1234".to_string()),
prefix: Some("prefix".to_string())
})
}
)
}
#[test]
@ -597,7 +596,7 @@ fn default_config_as_default() {
ifup: None,
ifdown: None,
crypto: CryptoConfig::default(),
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
listen: "[::]:3210".to_string(),
peers: vec![],
peer_timeout: 0,
keepalive: None,
@ -616,9 +615,10 @@ fn default_config_as_default() {
statsd_server: None,
statsd_prefix: None,
user: None,
group: None
group: None,
};
let default_config_file = serde_yaml::from_str::<ConfigFile>(include_str!("../assets/example.net.disabled")).unwrap();
let default_config_file =
serde_yaml::from_str::<ConfigFile>(include_str!("../assets/example.net.disabled")).unwrap();
default_config.merge_file(default_config_file);
assert_eq!(default_config, Config::default());
}
@ -631,7 +631,7 @@ fn config_merge() {
type_: Some(Type::Tun),
name: Some("vpncloud%d".to_string()),
path: None,
fix_rp_filter: None
fix_rp_filter: None,
}),
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
@ -645,7 +645,7 @@ fn config_merge() {
store: Some("/run/vpncloud.beacon.out".to_string()),
load: Some("/run/vpncloud.beacon.in".to_string()),
interval: Some(7200),
password: Some("test123".to_string())
password: Some("test123".to_string()),
}),
mode: Some(Mode::Normal),
switch_timeout: Some(300),
@ -658,36 +658,39 @@ fn config_merge() {
stats_file: Some("/var/log/vpncloud.stats".to_string()),
statsd: Some(ConfigFileStatsd {
server: Some("example.com:1234".to_string()),
prefix: Some("prefix".to_string())
})
});
assert_eq!(config, Config {
device_type: Type::Tun,
device_name: "vpncloud%d".to_string(),
device_path: None,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600,
keepalive: Some(840),
switch_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
beacon_password: Some("test123".to_string()),
mode: Mode::Normal,
port_forwarding: true,
claims: vec!["10.0.1.0/24".to_string()],
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()),
statsd_server: Some("example.com:1234".to_string()),
statsd_prefix: Some("prefix".to_string()),
..Default::default()
prefix: Some("prefix".to_string()),
}),
});
assert_eq!(
config,
Config {
device_type: Type::Tun,
device_name: "vpncloud%d".to_string(),
device_path: None,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
listen: "[::]:3210".to_string(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600,
keepalive: Some(840),
switch_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
beacon_password: Some("test123".to_string()),
mode: Mode::Normal,
port_forwarding: true,
claims: vec!["10.0.1.0/24".to_string()],
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()),
statsd_server: Some("example.com:1234".to_string()),
statsd_prefix: Some("prefix".to_string()),
..Default::default()
}
);
config.merge_args(Args {
type_: Some(Type::Tap),
device: Some("vpncloud0".to_string()),
@ -716,38 +719,41 @@ fn config_merge() {
group: Some("root".to_string()),
..Default::default()
});
assert_eq!(config, Config {
device_type: Type::Tap,
device_name: "vpncloud0".to_string(),
device_path: Some("/dev/null".to_string()),
fix_rp_filter: false,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
ifdown: Some("ifconfig $IFNAME down".to_string()),
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
listen: "[::]:3211".parse::<SocketAddr>().unwrap(),
peers: vec![
"remote.machine.foo:3210".to_string(),
"remote.machine.bar:3210".to_string(),
"another:3210".to_string()
],
peer_timeout: 1801,
keepalive: Some(850),
switch_timeout: 301,
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
beacon_interval: 3600,
beacon_password: Some("test1234".to_string()),
mode: Mode::Switch,
port_forwarding: false,
claims: vec!["10.0.1.0/24".to_string()],
auto_claim: true,
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()),
statsd_server: Some("example.com:2345".to_string()),
statsd_prefix: Some("prefix2".to_string()),
daemonize: true
});
assert_eq!(
config,
Config {
device_type: Type::Tap,
device_name: "vpncloud0".to_string(),
device_path: Some("/dev/null".to_string()),
fix_rp_filter: false,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
ifdown: Some("ifconfig $IFNAME down".to_string()),
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
listen: "[::]:3211".to_string(),
peers: vec![
"remote.machine.foo:3210".to_string(),
"remote.machine.bar:3210".to_string(),
"another:3210".to_string()
],
peer_timeout: 1801,
keepalive: Some(850),
switch_timeout: 301,
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
beacon_interval: 3600,
beacon_password: Some("test1234".to_string()),
mode: Mode::Switch,
port_forwarding: false,
claims: vec!["10.0.1.0/24".to_string()],
auto_claim: true,
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()),
statsd_server: Some("example.com:2345".to_string()),
statsd_prefix: Some("prefix2".to_string()),
daemonize: true
}
);
}

View File

@ -4,11 +4,15 @@
#![cfg_attr(feature = "bench", feature(test))]
#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[cfg(test)] extern crate tempfile;
#[cfg(feature = "bench")] extern crate test;
#[cfg(test)]
extern crate tempfile;
#[cfg(feature = "bench")]
extern crate test;
#[macro_use]
pub mod util;
@ -30,6 +34,7 @@ pub mod port_forwarding;
pub mod table;
pub mod traffic;
pub mod types;
pub mod wsproxy;
use structopt::StructOpt;
@ -39,26 +44,26 @@ use std::{
net::{Ipv4Addr, UdpSocket},
os::unix::fs::PermissionsExt,
path::Path,
process::Command,
process,
str::FromStr,
sync::Mutex,
thread
thread,
};
use crate::{
cloud::GenericCloud,
config::{Args, Config},
config::{Args, Command, Config},
crypto::Crypto,
device::{Device, TunTapDevice, Type},
net::Socket,
oldconfig::OldConfigFile,
payload::Protocol,
port_forwarding::PortForwarding,
util::SystemTimeSource
util::SystemTimeSource,
wsproxy::ProxyConnection,
};
struct DualLogger {
file: Option<Mutex<File>>
file: Option<Mutex<File>>,
}
impl DualLogger {
@ -105,7 +110,7 @@ impl log::Log for DualLogger {
}
fn run_script(script: &str, ifname: &str) {
let mut cmd = Command::new("sh");
let mut cmd = process::Command::new("sh");
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
debug!("Running script: {:?}", cmd);
match cmd.status() {
@ -114,18 +119,18 @@ fn run_script(script: &str, ifname: &str) {
error!("Script returned with error: {:?}", status.code())
}
}
Err(e) => error!("Failed to execute script {:?}: {}", script, e)
Err(e) => error!("Failed to execute script {:?}: {}", script, e),
}
}
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
let (ip_str, len_str) = match addr.find('/') {
Some(pos) => (&addr[..pos], &addr[pos + 1..]),
None => (addr, "24")
None => (addr, "24"),
};
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
if prefix_len > 32 {
return Err(format!("Invalid prefix length: {}", prefix_len))
return Err(format!("Invalid prefix length: {}", prefix_len));
}
let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?;
let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap());
@ -162,11 +167,10 @@ fn setup_device(config: &Config) -> TunTapDevice {
device
}
#[allow(clippy::cognitive_complexity)]
fn run<P: Protocol>(config: Config) {
fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
let device = setup_device(&config);
let port_forwarding = if config.port_forwarding { PortForwarding::new(config.listen.port()) } else { None };
let port_forwarding = if config.port_forwarding { socket.create_port_forwarding() } else { None };
let stats_file = match config.stats_file {
None => None,
Some(ref name) => {
@ -183,7 +187,7 @@ fn run<P: Protocol>(config: Config) {
}
};
let mut cloud =
GenericCloud::<TunTapDevice, P, UdpSocket, SystemTimeSource>::new(&config, device, port_forwarding, stats_file);
GenericCloud::<TunTapDevice, P, S, SystemTimeSource>::new(&config, socket, device, port_forwarding, stats_file);
for addr in config.peers {
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
cloud.add_reconnect_peer(addr);
@ -224,15 +228,7 @@ fn main() {
let args: Args = Args::from_args();
if args.version {
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
return
}
if args.genkey {
let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref());
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey);
println!(
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust."
);
return
return;
}
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
log::set_boxed_logger(Box::new(logger)).unwrap();
@ -244,24 +240,43 @@ fn main() {
} else {
log::LevelFilter::Info
});
if args.migrate_config {
let file = args.config.unwrap();
info!("Trying to convert from old config format");
let f = try_fail!(File::open(&file), "Failed to open config file: {:?}");
let config_file_old: OldConfigFile =
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
let new_config = config_file_old.convert();
info!("Successfully converted from old format");
info!("Renaming original file to {}.orig", file);
try_fail!(fs::rename(&file, format!("{}.orig", file)), "Failed to rename original file: {:?}");
info!("Writing new config back into {}", file);
let f = try_fail!(File::create(&file), "Failed to open config file: {:?}");
try_fail!(
fs::set_permissions(&file, fs::Permissions::from_mode(0o600)),
"Failed to set permissions on file: {:?}"
);
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
return
if let Some(cmd) = args.cmd {
match cmd {
Command::GenKey => {
let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref());
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey);
println!(
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust."
);
}
Command::MigrateConfig { config_file } => {
info!("Trying to convert from old config format");
let f = try_fail!(File::open(&config_file), "Failed to open config file: {:?}");
let config_file_old: OldConfigFile =
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
let new_config = config_file_old.convert();
info!("Successfully converted from old format");
info!("Renaming original file to {}.orig", config_file);
try_fail!(
fs::rename(&config_file, format!("{}.orig", config_file)),
"Failed to rename original file: {:?}"
);
info!("Writing new config back into {}", config_file);
let f = try_fail!(File::create(&config_file), "Failed to open config file: {:?}");
try_fail!(
fs::set_permissions(&config_file, fs::Permissions::from_mode(0o600)),
"Failed to set permissions on file: {:?}"
);
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
}
Command::WsProxy => {
wsproxy::run_proxy();
}
Command::WsClient { url } => {
wsproxy::run_client(url);
}
}
return;
}
let mut config = Config::default();
if let Some(ref file) = args.config {
@ -284,8 +299,21 @@ fn main() {
}
config.merge_args(args);
debug!("Config: {:?}", config);
match config.device_type {
Type::Tap => run::<payload::Frame>(config),
Type::Tun => run::<payload::Packet>(config)
if config.crypto.password.is_none() && config.crypto.private_key.is_none() {
error!("Either password or private key must be set in config or given as parameter");
return;
}
if config.listen.starts_with("ws") {
let socket = try_fail!(ProxyConnection::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
match config.device_type {
Type::Tap => run::<payload::Frame, _>(config, socket),
Type::Tun => run::<payload::Packet, _>(config, socket),
}
} else {
let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
match config.device_type {
Type::Tap => run::<payload::Frame, _>(config, socket),
Type::Tun => run::<payload::Packet, _>(config, socket),
}
}
}

View File

@ -5,12 +5,13 @@
use std::{
collections::{HashMap, VecDeque},
io::{self, ErrorKind},
net::{IpAddr, SocketAddr, UdpSocket},
net::{IpAddr, SocketAddr, UdpSocket, Ipv6Addr},
os::unix::io::{AsRawFd, RawFd},
sync::atomic::{AtomicBool, Ordering}
};
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
use crate::port_forwarding::PortForwarding;
pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
match addr {
@ -21,14 +22,28 @@ pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
pub trait Socket: AsRawFd + Sized {
fn listen(addr: SocketAddr) -> Result<Self, io::Error>;
fn listen(addr: &str) -> Result<Self, io::Error>;
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error>;
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>;
fn address(&self) -> Result<SocketAddr, io::Error>;
fn create_port_forwarding(&self) -> Option<PortForwarding>;
}
fn parse_listen(addr: &str) -> SocketAddr {
if let Some(addr) = addr.strip_prefix("*:") {
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
} else if addr.contains(':') {
try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr)
} else {
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
}
}
impl Socket for UdpSocket {
fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
fn listen(addr: &str) -> Result<Self, io::Error> {
let addr = parse_listen(addr);
UdpSocket::bind(addr)
}
@ -46,6 +61,10 @@ impl Socket for UdpSocket {
fn address(&self) -> Result<SocketAddr, io::Error> {
self.local_addr()
}
fn create_port_forwarding(&self) -> Option<PortForwarding> {
PortForwarding::new(self.address().unwrap().port())
}
}
thread_local! {
@ -106,8 +125,8 @@ impl AsRawFd for MockSocket {
}
impl Socket for MockSocket {
fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
Ok(Self::new(addr))
fn listen(addr: &str) -> Result<Self, io::Error> {
Ok(Self::new(parse_listen(addr)))
}
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
@ -132,6 +151,10 @@ impl Socket for MockSocket {
fn address(&self) -> Result<SocketAddr, io::Error> {
Ok(self.address)
}
fn create_port_forwarding(&self) -> Option<PortForwarding> {
None
}
}
#[cfg(feature = "bench")]

102
src/wsproxy.rs Normal file
View File

@ -0,0 +1,102 @@
use super::{
net::{mapped_addr, Socket},
port_forwarding::PortForwarding,
util::MsgBuffer
};
use std::{
io::{self, Write, Read, Cursor},
net::{SocketAddr, TcpListener, SocketAddrV6, Ipv6Addr},
os::unix::io::{AsRawFd, RawFd},
thread::spawn,
};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, stream::Stream, Message};
use url::Url;
pub fn run_proxy() {
let server = TcpListener::bind("127.0.0.1:9001").unwrap();
for stream in server.incoming() {
info!("connect");
spawn(move || {
let mut websocket = accept(stream.unwrap()).unwrap();
loop {
let msg = websocket.read_message().unwrap();
// We do not want to send back ping/pong messages.
if msg.is_binary() || msg.is_text() {
info!("msg");
websocket.write_message(msg).unwrap();
}
}
});
}
}
pub struct ProxyConnection {
url: String,
addr: SocketAddr,
socket: WebSocket<AutoStream>
}
impl AsRawFd for ProxyConnection {
fn as_raw_fd(&self) -> RawFd {
match self.socket.get_ref() {
Stream::Plain(sock) => sock.as_raw_fd(),
Stream::Tls(sock) => sock.get_ref().as_raw_fd()
}
}
}
impl Socket for ProxyConnection {
fn listen(url: &str) -> Result<Self, io::Error> {
let (mut socket, _) = connect(Url::parse(url).unwrap()).unwrap();
let addr = "0.0.0.0:0".parse::<SocketAddr>().unwrap();
Ok(ProxyConnection { url: addr.to_string(), addr, socket })
}
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
buffer.clear();
match self.socket.read_message().unwrap() {
Message::Binary(data) => {
let mut cursor = Cursor::new(&data);
let mut ip = [0u8; 16];
cursor.read_exact(&mut ip)?;
let port = cursor.read_u16::<NetworkEndian>()?;
let addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0));
buffer.clone_from(&data[18..]);
Ok(addr)
},
_ => unimplemented!()
}
}
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error> {
let mut msg = Vec::with_capacity(data.len() + 18);
let addr = mapped_addr(addr);
match mapped_addr(addr) {
SocketAddr::V6(addr) => {
msg.write_all(&addr.ip().octets())?;
msg.write_u16::<NetworkEndian>(addr.port())?;
},
_ => unreachable!()
}
msg.write_all(data)?;
self.socket.write_message(Message::Binary(msg)).unwrap();
Ok(data.len())
}
fn address(&self) -> Result<SocketAddr, io::Error> {
Ok(self.addr)
}
fn create_port_forwarding(&self) -> Option<PortForwarding> {
None
}
}
pub fn run_client(url: String) {
let (mut socket, _) = connect(Url::parse(&url).unwrap()).unwrap();
socket.write_message(Message::Text("test".to_string())).unwrap();
let msg = socket.read_message().unwrap();
info!("msg: {}", msg);
socket.close(None).unwrap();
}