From d50490ac517fc64fdd5aa559a09d16843e83e2c6 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Sun, 20 Dec 2020 01:40:32 +0100 Subject: [PATCH 01/25] Client works --- Cargo.lock | 241 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/cloud.rs | 6 +- src/config.rs | 290 +++++++++++++++++++++++++------------------------ src/main.rs | 126 ++++++++++++--------- src/net.rs | 33 +++++- src/wsproxy.rs | 102 +++++++++++++++++ 7 files changed, 599 insertions(+), 201 deletions(-) create mode 100644 src/wsproxy.rs diff --git a/Cargo.lock b/Cargo.lock index fda0d79..c5ea152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 9ff9d3a..31fe59f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ privdrop = "0.5" byteorder = "1.3" thiserror = "1.0" smallvec = "1.5" +tungstenite = "*" +url = "*" [dev-dependencies] tempfile = "3" diff --git a/src/cloud.rs b/src/cloud.rs index 1a3533a..23100ea 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -98,11 +98,7 @@ pub struct GenericCloud { impl GenericCloud { #[allow(clippy::too_many_arguments)] - pub fn new(config: &Config, device: D, port_forwarding: Option, stats_file: Option) -> 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, stats_file: Option) -> Self { let (learning, broadcast) = match config.mode { Mode::Normal => { match config.device_type { diff --git a/src/config.rs b/src/config.rs index cec0309..0958c92 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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::(), "Invalid port: {}"); - SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) - } else if addr.contains(':') { - try_fail!(addr.parse::(), "Invalid address: {}: {}", addr) - } else { - let port = try_fail!(addr.parse::(), "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, pub peer_timeout: Duration, pub keepalive: Option, @@ -60,7 +43,7 @@ pub struct Config { pub statsd_server: Option, pub statsd_prefix: Option, pub user: Option, - pub group: Option + pub group: Option, } impl Default for Config { @@ -74,7 +57,7 @@ impl Default for Config { ifup: None, ifdown: None, crypto: CryptoConfig::default(), - listen: "[::]:3210".parse::().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, /// 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, /// 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, - /// Migrate an old config file - #[structopt(long, alias = "migrate", requires = "config")] - pub migrate_config: bool + #[structopt(subcommand)] + pub cmd: Option, +} + +#[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, pub name: Option, pub path: Option, - pub fix_rp_filter: Option + pub fix_rp_filter: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] @@ -477,14 +474,14 @@ pub struct ConfigFileBeacon { pub store: Option, pub load: Option, pub interval: Option, - pub password: Option + pub password: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[serde(rename_all = "kebab-case", deny_unknown_fields, default)] pub struct ConfigFileStatsd { pub server: Option, - pub prefix: Option + pub prefix: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] @@ -512,10 +509,9 @@ pub struct ConfigFile { pub stats_file: Option, pub statsd: Option, pub user: Option, - pub group: Option + pub group: Option, } - #[test] fn config_file() { let config_file = " @@ -549,41 +545,44 @@ statsd: server: example.com:1234 prefix: prefix "; - assert_eq!(serde_yaml::from_str::(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::(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::().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::(include_str!("../assets/example.net.disabled")).unwrap(); + let default_config_file = + serde_yaml::from_str::(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::().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::().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 + } + ); } diff --git a/src/main.rs b/src/main.rs index 156f17d..7150c76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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> + file: Option>, } 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(config: Config) { +fn run(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(config: Config) { } }; let mut cloud = - GenericCloud::::new(&config, device, port_forwarding, stats_file); + GenericCloud::::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::(config), - Type::Tun => run::(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::(config, socket), + Type::Tun => run::(config, socket), + } + } else { + let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen); + match config.device_type { + Type::Tap => run::(config, socket), + Type::Tun => run::(config, socket), + } } } diff --git a/src/net.rs b/src/net.rs index 296c6e9..00b4611 100644 --- a/src/net.rs +++ b/src/net.rs @@ -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; + fn listen(addr: &str) -> Result; fn receive(&mut self, buffer: &mut MsgBuffer) -> Result; fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result; fn address(&self) -> Result; + fn create_port_forwarding(&self) -> Option; +} + +fn parse_listen(addr: &str) -> SocketAddr { + if let Some(addr) = addr.strip_prefix("*:") { + let port = try_fail!(addr.parse::(), "Invalid port: {}"); + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) + } else if addr.contains(':') { + try_fail!(addr.parse::(), "Invalid address: {}: {}", addr) + } else { + let port = try_fail!(addr.parse::(), "Invalid port: {}"); + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) + } } impl Socket for UdpSocket { - fn listen(addr: SocketAddr) -> Result { + fn listen(addr: &str) -> Result { + let addr = parse_listen(addr); UdpSocket::bind(addr) } @@ -46,6 +61,10 @@ impl Socket for UdpSocket { fn address(&self) -> Result { self.local_addr() } + + fn create_port_forwarding(&self) -> Option { + 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 { - Ok(Self::new(addr)) + fn listen(addr: &str) -> Result { + Ok(Self::new(parse_listen(addr))) } fn receive(&mut self, buffer: &mut MsgBuffer) -> Result { @@ -132,6 +151,10 @@ impl Socket for MockSocket { fn address(&self) -> Result { Ok(self.address) } + + fn create_port_forwarding(&self) -> Option { + None + } } #[cfg(feature = "bench")] diff --git a/src/wsproxy.rs b/src/wsproxy.rs new file mode 100644 index 0000000..c387640 --- /dev/null +++ b/src/wsproxy.rs @@ -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 +} + +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 { + let (mut socket, _) = connect(Url::parse(url).unwrap()).unwrap(); + let addr = "0.0.0.0:0".parse::().unwrap(); + Ok(ProxyConnection { url: addr.to_string(), addr, socket }) + } + + fn receive(&mut self, buffer: &mut MsgBuffer) -> Result { + 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::()?; + 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 { + 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::(addr.port())?; + }, + _ => unreachable!() + } + msg.write_all(data)?; + self.socket.write_message(Message::Binary(msg)).unwrap(); + Ok(data.len()) + } + + fn address(&self) -> Result { + Ok(self.addr) + } + + fn create_port_forwarding(&self) -> Option { + 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(); +} From 124f7cbff9dd60e29ed078080841e237f86386a5 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Sun, 20 Dec 2020 13:28:01 +0100 Subject: [PATCH 02/25] Almost works --- src/cloud.rs | 2 +- src/main.rs | 2 +- src/poll/epoll.rs | 12 ++-- src/wsproxy.rs | 156 +++++++++++++++++++++++++++++++++------------- 4 files changed, 121 insertions(+), 51 deletions(-) diff --git a/src/cloud.rs b/src/cloud.rs index 23100ea..ddb99f3 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -824,7 +824,7 @@ impl GenericCloud run::(config, socket), diff --git a/src/poll/epoll.rs b/src/poll/epoll.rs index 5d69d43..73679de 100644 --- a/src/poll/epoll.rs +++ b/src/poll/epoll.rs @@ -2,11 +2,9 @@ // Copyright (C) 2015-2020 Dennis Schwerdel // This software is licensed under GPL-3 or newer (see LICENSE.md) -use crate::device::Device; use std::{io, os::unix::io::RawFd}; use super::WaitResult; -use crate::net::Socket; pub struct EpollWait { poll_fd: RawFd, @@ -17,21 +15,21 @@ pub struct EpollWait { } impl EpollWait { - pub fn new(socket: &S, device: &dyn Device, timeout: u32) -> io::Result { + pub fn new(socket: RawFd, device: RawFd, timeout: u32) -> io::Result { Self::create(socket, device, timeout, libc::EPOLLIN as u32) } - pub fn testing(socket: &S, device: &dyn Device, timeout: u32) -> io::Result { + pub fn testing(socket: RawFd, device: RawFd, timeout: u32) -> io::Result { Self::create(socket, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32) } - fn create(socket: &S, device: &dyn Device, timeout: u32, flags: u32) -> io::Result { + fn create(socket: RawFd, device: RawFd, timeout: u32, flags: u32) -> io::Result { let mut event = libc::epoll_event { u64: 0, events: 0 }; let poll_fd = unsafe { libc::epoll_create(3) }; if poll_fd == -1 { return Err(io::Error::last_os_error()) } - for fd in &[socket.as_raw_fd(), device.as_raw_fd()] { + for fd in &[socket, device] { event.u64 = *fd as u64; event.events = flags; let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) }; @@ -39,7 +37,7 @@ impl EpollWait { return Err(io::Error::last_os_error()) } } - Ok(Self { poll_fd, event, socket: socket.as_raw_fd(), device: device.as_raw_fd(), timeout }) + Ok(Self { poll_fd, event, socket, device, timeout }) } } diff --git a/src/wsproxy.rs b/src/wsproxy.rs index c387640..25f7c82 100644 --- a/src/wsproxy.rs +++ b/src/wsproxy.rs @@ -1,42 +1,125 @@ use super::{ net::{mapped_addr, Socket}, + poll::{WaitImpl, WaitResult}, 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 std::{ + io::{self, Cursor, Read, Write}, + net::{Ipv6Addr, SocketAddr, SocketAddrV6, TcpListener, TcpStream, UdpSocket}, + os::unix::io::{AsRawFd, RawFd}, + thread::spawn +}; 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(); + +macro_rules! io_error { + ($val:expr, $format:expr) => ( { + $val.map_err(|err| io::Error::new(io::ErrorKind::Other, format!($format, err))) + } ); + ($val:expr, $format:expr, $( $arg:expr ),+) => ( { + $val.map_err(|err| io::Error::new(io::ErrorKind::Other, format!($format, $( $arg ),+, err))) + } ); +} + +fn write_addr(addr: SocketAddr, mut out: W) -> Result<(), io::Error> { + let addr = mapped_addr(addr); + match mapped_addr(addr) { + SocketAddr::V6(addr) => { + out.write_all(&addr.ip().octets())?; + out.write_u16::(addr.port())?; + } + _ => unreachable!() + } + Ok(()) +} + +fn read_addr(mut r: R) -> Result { + let mut ip = [0u8; 16]; + r.read_exact(&mut ip)?; + let port = r.read_u16::()?; + let addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0)); + Ok(addr) +} + +fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { + let peer = stream.peer_addr()?; + info!("WS client {} connected", peer); + let mut websocket = io_error!(accept(stream), "Failed to initialize websocket with {}: {}", peer)?; + let udpsocket = UdpSocket::bind("[::]:0")?; + let mut msg = Vec::with_capacity(18); + let addr = udpsocket.local_addr()?; + info!("Listening on {} for peer {}", addr, peer); + write_addr(addr, &mut msg)?; + io_error!(websocket.write_message(Message::Binary(msg)), "Failed to write to ws connection: {}")?; + let websocketfd = websocket.get_ref().as_raw_fd(); + let poll = WaitImpl::new(websocketfd, udpsocket.as_raw_fd(), 60)?; + let mut buffer = [0; 65535]; + for evt in poll { + match evt { + WaitResult::Socket => { + info!("WS -> UDP"); + let msg = io_error!(websocket.read_message(), "Failed to read message on websocket {}: {}", peer)?; + info!("MSG: {}", msg); + match msg { + Message::Binary(data) => { + let dst = read_addr(Cursor::new(&data))?; + udpsocket.send_to(&data[18..], dst)?; + } + Message::Close(_) => return Ok(()), + _ => {} } } + WaitResult::Device => { + info!("UDP -> WS"); + let (size, addr) = udpsocket.recv_from(&mut buffer)?; + let mut data = Vec::with_capacity(18 + size); + write_addr(addr, &mut data)?; + data.write_all(&buffer[..size])?; + io_error!(websocket.write_message(Message::Binary(data)), "Failed to write to {}: {}", peer)?; + } + WaitResult::Timeout => { + info!("Sending ping"); + io_error!(websocket.write_message(Message::Ping(vec![])), "Failed to send ping: {}")?; + } + WaitResult::Error(err) => return Err(err) + } + } + Ok(()) +} + +pub fn run_proxy() -> Result<(), io::Error> { + // TODO: configurable listen + let server = TcpListener::bind("127.0.0.1:9001")?; + for stream in server.incoming() { + let stream = stream?; + let peer = stream.peer_addr()?; + spawn(move || { + if let Err(err) = serve_proxy_connection(stream) { + error!("Error on connection {}: {}", peer, err); + } }); } + Ok(()) } pub struct ProxyConnection { - url: String, addr: SocketAddr, socket: WebSocket } +impl ProxyConnection { + fn read_message(&mut self) -> Result, io::Error> { + loop { + if let Message::Binary(data) = io_error!(self.socket.read_message(), "Failed to read from ws proxy: {}")? { + return Ok(data) + } + } + } +} + impl AsRawFd for ProxyConnection { fn as_raw_fd(&self) -> RawFd { match self.socket.get_ref() { @@ -48,39 +131,28 @@ impl AsRawFd for ProxyConnection { impl Socket for ProxyConnection { fn listen(url: &str) -> Result { - let (mut socket, _) = connect(Url::parse(url).unwrap()).unwrap(); + let parsed_url = io_error!(Url::parse(url), "Invalid URL {}: {}", url)?; + let (socket, _) = io_error!(connect(parsed_url), "Failed to connect to URL {}: {}", url)?; let addr = "0.0.0.0:0".parse::().unwrap(); - Ok(ProxyConnection { url: addr.to_string(), addr, socket }) + let mut con = ProxyConnection { addr, socket }; + let addr_data = con.read_message()?; + con.addr = read_addr(Cursor::new(&addr_data))?; + Ok(con) } fn receive(&mut self, buffer: &mut MsgBuffer) -> Result { 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::()?; - let addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0)); - buffer.clone_from(&data[18..]); - Ok(addr) - }, - _ => unimplemented!() - } + let data = self.read_message()?; + let addr = read_addr(Cursor::new(&data))?; + buffer.clone_from(&data[18..]); + Ok(addr) } fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result { 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::(addr.port())?; - }, - _ => unreachable!() - } + write_addr(addr, &mut msg)?; msg.write_all(data)?; - self.socket.write_message(Message::Binary(msg)).unwrap(); + io_error!(self.socket.write_message(Message::Binary(msg)), "Failed to write to ws proxy: {}")?; Ok(data.len()) } From 7f135fbf24669d293b5b7f33fb40c3a9d4dbaee3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Jan 2021 18:29:03 +0000 Subject: [PATCH 03/25] Bump libc from 0.2.83 to 0.2.84 Bumps [libc](https://github.com/rust-lang/libc) from 0.2.83 to 0.2.84. - [Release notes](https://github.com/rust-lang/libc/releases) - [Commits](https://github.com/rust-lang/libc/commits) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 207cf33..1829e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,9 +393,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0c4e9c72ee9d69b767adebc5f4788462a3b45624acd919475c92597bcaf4f" +checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" [[package]] name = "linked-hash-map" From 79c4f4ef9f42e8c2835b64fda707b774c712e6d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Feb 2021 05:21:33 +0000 Subject: [PATCH 04/25] Bump ring from 0.16.19 to 0.16.20 Bumps [ring](https://github.com/briansmith/ring) from 0.16.19 to 0.16.20. - [Release notes](https://github.com/briansmith/ring/releases) - [Commits](https://github.com/briansmith/ring/commits) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1829e0f..d6f514a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -696,9 +696,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.19" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", From f637af0faa2d746032d20dd9addc92d667b6b90e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Feb 2021 05:13:39 +0000 Subject: [PATCH 05/25] Bump libc from 0.2.84 to 0.2.85 Bumps [libc](https://github.com/rust-lang/libc) from 0.2.84 to 0.2.85. - [Release notes](https://github.com/rust-lang/libc/releases) - [Commits](https://github.com/rust-lang/libc/compare/0.2.84...0.2.85) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6f514a..f5ff0ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,9 +393,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" [[package]] name = "linked-hash-map" From 221d4ed490921c57593bcf3cc9e61f907e659ef3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Feb 2021 05:13:41 +0000 Subject: [PATCH 06/25] Bump serde_yaml from 0.8.15 to 0.8.16 Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.15 to 0.8.16. - [Release notes](https://github.com/dtolnay/serde-yaml/releases) - [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.15...0.8.16) Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6f514a..4a2eee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -797,9 +797,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f" +checksum = "bdd2af560da3c1fdc02cb80965289254fc35dff869810061e2d8290ee48848ae" dependencies = [ "dtoa", "linked-hash-map", From e3fa631ed9bb684e00eb87aa98e4951c63011739 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Wed, 3 Feb 2021 22:03:42 +0100 Subject: [PATCH 07/25] Working example --- src/cloud.rs | 1 + src/config.rs | 4 ---- src/main.rs | 3 --- src/net.rs | 9 ++++++++- src/wsproxy.rs | 21 +++++---------------- 5 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/cloud.rs b/src/cloud.rs index ddb99f3..49797d5 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -221,6 +221,7 @@ impl GenericCloud { wsproxy::run_proxy(); } - Command::WsClient { url } => { - wsproxy::run_client(url); - } } return; } diff --git a/src/net.rs b/src/net.rs index 00b4611..6ea964a 100644 --- a/src/net.rs +++ b/src/net.rs @@ -20,6 +20,11 @@ pub fn mapped_addr(addr: SocketAddr) -> SocketAddr { } } +pub fn get_ip() -> IpAddr { + let s = UdpSocket::bind("[::]:0").unwrap(); + s.connect("8.8.8.8:0").unwrap(); + s.local_addr().unwrap().ip() +} pub trait Socket: AsRawFd + Sized { fn listen(addr: &str) -> Result; @@ -59,7 +64,9 @@ impl Socket for UdpSocket { } fn address(&self) -> Result { - self.local_addr() + let mut addr = self.local_addr()?; + addr.set_ip(get_ip()); + Ok(addr) } fn create_port_forwarding(&self) -> Option { diff --git a/src/wsproxy.rs b/src/wsproxy.rs index 25f7c82..18548d9 100644 --- a/src/wsproxy.rs +++ b/src/wsproxy.rs @@ -1,5 +1,5 @@ use super::{ - net::{mapped_addr, Socket}, + net::{mapped_addr, get_ip, Socket}, poll::{WaitImpl, WaitResult}, port_forwarding::PortForwarding, util::MsgBuffer @@ -50,19 +50,18 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { let mut websocket = io_error!(accept(stream), "Failed to initialize websocket with {}: {}", peer)?; let udpsocket = UdpSocket::bind("[::]:0")?; let mut msg = Vec::with_capacity(18); - let addr = udpsocket.local_addr()?; + let mut addr = udpsocket.local_addr()?; info!("Listening on {} for peer {}", addr, peer); + addr.set_ip(get_ip()); write_addr(addr, &mut msg)?; io_error!(websocket.write_message(Message::Binary(msg)), "Failed to write to ws connection: {}")?; let websocketfd = websocket.get_ref().as_raw_fd(); - let poll = WaitImpl::new(websocketfd, udpsocket.as_raw_fd(), 60)?; + let poll = WaitImpl::new(websocketfd, udpsocket.as_raw_fd(), 60*1000)?; let mut buffer = [0; 65535]; for evt in poll { match evt { WaitResult::Socket => { - info!("WS -> UDP"); let msg = io_error!(websocket.read_message(), "Failed to read message on websocket {}: {}", peer)?; - info!("MSG: {}", msg); match msg { Message::Binary(data) => { let dst = read_addr(Cursor::new(&data))?; @@ -73,7 +72,6 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { } } WaitResult::Device => { - info!("UDP -> WS"); let (size, addr) = udpsocket.recv_from(&mut buffer)?; let mut data = Vec::with_capacity(18 + size); write_addr(addr, &mut data)?; @@ -81,7 +79,6 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { io_error!(websocket.write_message(Message::Binary(data)), "Failed to write to {}: {}", peer)?; } WaitResult::Timeout => { - info!("Sending ping"); io_error!(websocket.write_message(Message::Ping(vec![])), "Failed to send ping: {}")?; } WaitResult::Error(err) => return Err(err) @@ -163,12 +160,4 @@ impl Socket for ProxyConnection { fn create_port_forwarding(&self) -> Option { 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(); -} +} \ No newline at end of file From a113b3ba22d60022f27f6f25188896044f3195fe Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Wed, 3 Feb 2021 22:46:53 +0100 Subject: [PATCH 08/25] Remove unused imports --- src/config.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index a3023ab..011ddfd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,6 @@ use std::{ cmp::max, collections::HashMap, ffi::OsStr, - net::{IpAddr, Ipv6Addr, SocketAddr}, process, thread }; From bd0d102358b1d0815a362071b577584ed84903b0 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Thu, 4 Feb 2021 21:19:05 +0100 Subject: [PATCH 09/25] Add configurable WS port --- src/config.rs | 8 +++++++- src/main.rs | 4 ++-- src/net.rs | 2 +- src/wsproxy.rs | 8 ++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index 011ddfd..13a8bfd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -499,7 +499,13 @@ pub enum Command { #[structopt(name = "genkey", alias = "gen-key")] GenKey, - WsProxy, + /// Run a websocket proxy + #[structopt(alias = "wsproxy")] + WsProxy { + /// Websocket listen address IP:PORT + #[structopt(long, short, default_value="3210")] + listen: String + }, /// Migrate an old config file #[structopt(alias = "migrate")] diff --git a/src/main.rs b/src/main.rs index cb39a4e..62d658d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -268,8 +268,8 @@ fn main() { Args::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut io::stdout()); return } - Command::WsProxy => { - wsproxy::run_proxy(); + Command::WsProxy { listen } => { + try_fail!(wsproxy::run_proxy(&listen), "Failed to run websocket proxy: {:?}"); } } return diff --git a/src/net.rs b/src/net.rs index c2ac39f..d513edc 100644 --- a/src/net.rs +++ b/src/net.rs @@ -35,7 +35,7 @@ pub trait Socket: AsRawFd + Sized { fn create_port_forwarding(&self) -> Option; } -fn parse_listen(addr: &str) -> SocketAddr { +pub fn parse_listen(addr: &str) -> SocketAddr { if let Some(addr) = addr.strip_prefix("*:") { let port = try_fail!(addr.parse::(), "Invalid port: {}"); SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) diff --git a/src/wsproxy.rs b/src/wsproxy.rs index 18548d9..775059c 100644 --- a/src/wsproxy.rs +++ b/src/wsproxy.rs @@ -1,5 +1,5 @@ use super::{ - net::{mapped_addr, get_ip, Socket}, + net::{mapped_addr, get_ip, parse_listen, Socket}, poll::{WaitImpl, WaitResult}, port_forwarding::PortForwarding, util::MsgBuffer @@ -87,9 +87,9 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { Ok(()) } -pub fn run_proxy() -> Result<(), io::Error> { - // TODO: configurable listen - let server = TcpListener::bind("127.0.0.1:9001")?; +pub fn run_proxy(listen: &str) -> Result<(), io::Error> { + let addr = parse_listen(listen); + let server = TcpListener::bind(addr)?; for stream in server.incoming() { let stream = stream?; let peer = stream.peer_addr()?; From e9122743e9de701a51cac2fbcddee0082cf781f3 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Thu, 4 Feb 2021 21:42:38 +0100 Subject: [PATCH 10/25] Feature gate websockets --- Cargo.toml | 6 +++--- src/config.rs | 1 + src/main.rs | 20 ++++++++++++-------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2eebdcf..01a017f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ privdrop = "0.5" byteorder = "1.4" thiserror = "1.0" smallvec = "1.6" -tungstenite = "0.12" -url = "2.2" +tungstenite = { version = "0.12", optional = true } +url = { version = "2.2", optional = true } [dev-dependencies] tempfile = "3" @@ -38,8 +38,8 @@ criterion = "0.3" [features] default = ["nat"] -bench = [] nat = ["igd"] +websocket = ["tungstenite", "url"] [[bench]] name = "bench" diff --git a/src/config.rs b/src/config.rs index 13a8bfd..cfb59f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -500,6 +500,7 @@ pub enum Command { GenKey, /// Run a websocket proxy + #[cfg(feature = "websocket")] #[structopt(alias = "wsproxy")] WsProxy { /// Websocket listen address IP:PORT diff --git a/src/main.rs b/src/main.rs index 62d658d..9cdd511 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ pub mod port_forwarding; pub mod table; pub mod traffic; pub mod types; -pub mod wsproxy; +#[cfg(feature = "websocket")] pub mod wsproxy; use structopt::StructOpt; @@ -52,9 +52,11 @@ use crate::{ oldconfig::OldConfigFile, payload::Protocol, util::SystemTimeSource, - wsproxy::ProxyConnection }; +#[cfg(feature = "websocket")] +use crate::wsproxy::ProxyConnection; + struct DualLogger { file: Option> } @@ -268,6 +270,7 @@ fn main() { Args::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut io::stdout()); return } + #[cfg(feature = "websocket")] Command::WsProxy { listen } => { try_fail!(wsproxy::run_proxy(&listen), "Failed to run websocket proxy: {:?}"); } @@ -299,17 +302,18 @@ fn main() { error!("Either password or private key must be set in config or given as parameter"); return } + #[cfg(feature = "websocket")] 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::(config, socket), Type::Tun => run::(config, socket) } - } else { - let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen); - match config.device_type { - Type::Tap => run::(config, socket), - Type::Tun => run::(config, socket) - } + return + } + let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen); + match config.device_type { + Type::Tap => run::(config, socket), + Type::Tun => run::(config, socket) } } From 791ecfb0fe219172d0749667a82e4986f7f959e0 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Thu, 4 Feb 2021 22:12:20 +0100 Subject: [PATCH 11/25] Remove tls support on client --- Cargo.lock | 128 ------------------------------------------------- Cargo.toml | 4 +- src/wsproxy.rs | 7 +-- 3 files changed, 4 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 473e8c3..ab39a83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,22 +149,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" -[[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" @@ -318,21 +302,6 @@ 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" @@ -519,24 +488,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "native-tls" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.14.1" @@ -599,51 +550,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" -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.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" -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 = "plotters" version = "0.3.0" @@ -882,45 +794,12 @@ dependencies = [ "winapi-util", ] -[[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 = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[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" @@ -1248,7 +1127,6 @@ dependencies = [ "httparse", "input_buffer", "log", - "native-tls", "rand", "sha-1", "url", @@ -1321,12 +1199,6 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 01a017f..69292f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ privdrop = "0.5" byteorder = "1.4" thiserror = "1.0" smallvec = "1.6" -tungstenite = { version = "0.12", optional = true } +tungstenite = { version = "0.12", optional = true, default-features = false } url = { version = "2.2", optional = true } [dev-dependencies] @@ -37,7 +37,7 @@ tempfile = "3" criterion = "0.3" [features] -default = ["nat"] +default = ["nat", "websocket"] nat = ["igd"] websocket = ["tungstenite", "url"] diff --git a/src/wsproxy.rs b/src/wsproxy.rs index 775059c..1013d55 100644 --- a/src/wsproxy.rs +++ b/src/wsproxy.rs @@ -11,7 +11,7 @@ use std::{ os::unix::io::{AsRawFd, RawFd}, thread::spawn }; -use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, stream::Stream, Message}; +use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, Message}; use url::Url; @@ -119,10 +119,7 @@ impl ProxyConnection { 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() - } + self.socket.get_ref().as_raw_fd() } } From 65eef143cdbeae2637d9d8c2b76ee1a2ed44b220 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Thu, 4 Feb 2021 23:38:08 +0100 Subject: [PATCH 12/25] Small improvements --- contrib/performance.py | 2 +- contrib/testnet.py | 13 ++++++++++--- src/config.rs | 2 +- src/tests/mod.rs | 3 +-- src/wsproxy.rs | 11 +++++++---- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/contrib/performance.py b/contrib/performance.py index 5c49a1f..2da82bf 100755 --- a/contrib/performance.py +++ b/contrib/performance.py @@ -8,7 +8,7 @@ from datetime import date # Note: this script will run for ~8 minutes and incur costs of about $ 0.02 FILE = "../target/release/vpncloud" -VERSION = "2.0.0-alpha1" +VERSION = "2.0.1" REGION = "eu-central-1" env = EC2Environment( diff --git a/contrib/testnet.py b/contrib/testnet.py index 77a2802..debb418 100755 --- a/contrib/testnet.py +++ b/contrib/testnet.py @@ -5,7 +5,7 @@ import atexit, argparse, os REGION = "eu-central-1" -VERSION = "2.0.0" +VERSION = "2.0.1" parser = argparse.ArgumentParser(description='Create a test setup') @@ -25,15 +25,22 @@ if args.keyname: with open(args.keyfile, 'r') as fp: privatekey = fp.read() +opts = {} +if os.path.exists(args.version): + opts["vpncloud_file"] = args.version + opts["vpncloud_version"] = None +else: + opts["vpncloud_version"] = args.version + setup = EC2Environment( region = REGION, node_count = args.count, instance_type = args.instancetype, - vpncloud_version = args.version, cluster_nodes = args.cluster, subnet = args.subnet or CREATE, keyname = args.keyname or CREATE, - privatekey = privatekey + privatekey = privatekey, + **opts ) if not args.keyname: diff --git a/src/config.rs b/src/config.rs index cfb59f2..35d55e0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -358,7 +358,7 @@ pub struct Args { pub mode: Option, /// The shared password to encrypt all traffic - #[structopt(short, long, required_unless_one = &["private-key", "config", "genkey", "version", "completion"], env)] + #[structopt(short, long, env)] pub password: Option, /// The private key to use diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 6632bc6..e42ed44 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -10,7 +10,6 @@ use std::{ collections::{HashMap, VecDeque}, io::Write, net::SocketAddr, - str::FromStr, sync::{ atomic::{AtomicUsize, Ordering}, Once @@ -91,12 +90,12 @@ impl Simulator

{ let mut config = config.clone(); MockSocket::set_nat(nat); config.listen = format!("[::]:{}", self.next_port); + let addr = config.listen.parse::().unwrap(); if config.crypto.password.is_none() && config.crypto.private_key.is_none() { config.crypto.password = Some("test123".to_string()) } DebugLogger::set_node(self.next_port as usize); self.next_port += 1; - let addr = SocketAddr::from_str(&config.listen).unwrap(); let node = TestNode::new(&config, MockSocket::new(addr), MockDevice::new(), None, None); DebugLogger::set_node(0); self.nodes.insert(addr, node); diff --git a/src/wsproxy.rs b/src/wsproxy.rs index 1013d55..15f8447 100644 --- a/src/wsproxy.rs +++ b/src/wsproxy.rs @@ -1,5 +1,5 @@ use super::{ - net::{mapped_addr, get_ip, parse_listen, Socket}, + net::{get_ip, mapped_addr, parse_listen, Socket}, poll::{WaitImpl, WaitResult}, port_forwarding::PortForwarding, util::MsgBuffer @@ -47,6 +47,7 @@ fn read_addr(mut r: R) -> Result { fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { let peer = stream.peer_addr()?; info!("WS client {} connected", peer); + stream.set_nodelay(true)?; let mut websocket = io_error!(accept(stream), "Failed to initialize websocket with {}: {}", peer)?; let udpsocket = UdpSocket::bind("[::]:0")?; let mut msg = Vec::with_capacity(18); @@ -56,7 +57,7 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { write_addr(addr, &mut msg)?; io_error!(websocket.write_message(Message::Binary(msg)), "Failed to write to ws connection: {}")?; let websocketfd = websocket.get_ref().as_raw_fd(); - let poll = WaitImpl::new(websocketfd, udpsocket.as_raw_fd(), 60*1000)?; + let poll = WaitImpl::new(websocketfd, udpsocket.as_raw_fd(), 60 * 1000)?; let mut buffer = [0; 65535]; for evt in poll { match evt { @@ -90,6 +91,7 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> { pub fn run_proxy(listen: &str) -> Result<(), io::Error> { let addr = parse_listen(listen); let server = TcpListener::bind(addr)?; + info!("Listening on ws://{}", server.local_addr()?); for stream in server.incoming() { let stream = stream?; let peer = stream.peer_addr()?; @@ -126,7 +128,8 @@ impl AsRawFd for ProxyConnection { impl Socket for ProxyConnection { fn listen(url: &str) -> Result { let parsed_url = io_error!(Url::parse(url), "Invalid URL {}: {}", url)?; - let (socket, _) = io_error!(connect(parsed_url), "Failed to connect to URL {}: {}", url)?; + let (mut socket, _) = io_error!(connect(parsed_url), "Failed to connect to URL {}: {}", url)?; + socket.get_mut().set_nodelay(true)?; let addr = "0.0.0.0:0".parse::().unwrap(); let mut con = ProxyConnection { addr, socket }; let addr_data = con.read_message()?; @@ -157,4 +160,4 @@ impl Socket for ProxyConnection { fn create_port_forwarding(&self) -> Option { None } -} \ No newline at end of file +} From d154f85ecd64bfe01002c911fb828b7c96c194f2 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Thu, 4 Feb 2021 23:42:51 +0100 Subject: [PATCH 13/25] Changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1929536..52183f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ This project follows [semantic versioning](http://semver.org). ### UNRELEASED -- [added] Support for creating shell completions +- [added] Support for websocket proxy mode - [added] Support for hook scripts to handle certain situations +- [added] Support for creating shell completions - [removed] Removed dummy device type - [changed] Updated dependencies - [changed] Changed Rust version to 1.49.0 From 13688edd75986da538a6ae8ccba19817258a694d Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Fri, 5 Feb 2021 18:27:48 +0100 Subject: [PATCH 14/25] Prepare release --- CHANGELOG.md | 1 + Cargo.lock | 8 +-- src/cloud.rs | 7 +- src/config.rs | 8 ++- src/main.rs | 12 ++-- vpncloud.adoc | 180 ++++++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 174 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52183f0..acff1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project follows [semantic versioning](http://semver.org). - [changed] Updated dependencies - [changed] Changed Rust version to 1.49.0 - [fixed] Added missing peer address propagation +- [fixed] Fixed problem with peer addresses without port ### v2.0.1 (2020-11-07) diff --git a/Cargo.lock b/Cargo.lock index ab39a83..33c8ea8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,9 +376,9 @@ checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" [[package]] name = "idna" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +checksum = "de910d521f7cc3135c4de8db1cb910e0b5ed1dc6f57c381cd07e8e661ce10094" dependencies = [ "matches", "unicode-bidi", @@ -911,9 +911,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "standback" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a8cff4fa24853fdf6b51f75c6d7f8206d7c75cab4e467bcd7f25c2b1febe0" +checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8" dependencies = [ "version_check", ] diff --git a/src/cloud.rs b/src/cloud.rs index 9281f4e..7bcc0eb 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -242,12 +242,8 @@ impl GenericCloud addrs, Err(err) => { @@ -465,7 +461,6 @@ impl GenericCloud, + }, /// Run a websocket proxy #[cfg(feature = "websocket")] @@ -519,7 +523,7 @@ pub enum Command { /// Generate shell completions Completion { /// Shell to create completions for - #[structopt(long)] + #[structopt(long, default_value="bash")] shell: Shell } } diff --git a/src/main.rs b/src/main.rs index 9cdd511..6d59069 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ use std::{ use crate::{ cloud::GenericCloud, - config::{Args, Command, Config}, + config::{Args, Command, Config, DEFAULT_PORT}, crypto::Crypto, device::{Device, TunTapDevice, Type}, net::Socket, @@ -185,7 +185,11 @@ fn run(config: Config, socket: S) { }; let mut cloud = GenericCloud::::new(&config, socket, device, port_forwarding, stats_file); - for addr in config.peers { + for mut addr in config.peers { + if addr.find(':').unwrap_or(0) <= addr.find(']').unwrap_or(0) { + // : not present or only in IPv6 address + addr = format!("{}:{}", addr, DEFAULT_PORT) + } try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr); cloud.add_reconnect_peer(addr); } @@ -239,8 +243,8 @@ fn main() { }); if let Some(cmd) = args.cmd { match cmd { - Command::GenKey => { - let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref()); + Command::GenKey { password } => { + let (privkey, pubkey) = Crypto::generate_keypair(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." diff --git a/vpncloud.adoc b/vpncloud.adoc index 4871191..c1f162a 100644 --- a/vpncloud.adoc +++ b/vpncloud.adoc @@ -45,7 +45,10 @@ vpncloud - Peer-to-peer VPN The address on which to listen for data. This can be simply a port number or a full address in form IP:PORT. If the IP is specified as \'\*' or only a port number is given, then the socket will listen on all IPs (v4 and v6), - otherwise the socket will only listen on the given IP. [default: **3210**] + otherwise the socket will only listen on the given IP. + Alternatively, a websocket proxy URL (starting with ws://) can be given + here. Please see the section *WEBSOCKET PROXY* for more info. + [default: **3210**] *-c *, *--peer *, *--connect *:: Address of a peer to connect to. The address should be in the form @@ -62,31 +65,27 @@ vpncloud - Peer-to-peer VPN Do not automatically claim the IP set on the virtual interface (on TUN devices). -*-p *, *--password *:: +*-p *, *--password *:: A password to encrypt the VPN data. This parameter must be set unless a password is given in a config file or a private key is set. See *SECURITY* for more info. *--key *, *--private-key *:: A private key to use for encryption. The key must be given as base62 as - generated by *--genkey*. See *SECURITY* for more info. + generated by *genkey*. See *SECURITY* for more info. *--public-key *:: A public key matching the given private key. The key must be given as base62 - as generated by *--genkey*. This argument is purely optional. See *SECURITY* + as generated by *genkey*. This argument is purely optional. See *SECURITY* for more info. *--trust *, **--trusted-key *:: A public key to trust. Any peer must have a key pair that is trusted by this node, otherwise it will be rejected. The key must be given as base62 as - generated by *--genkey*. This argument can be given multiple times. If it is + generated by *genkey*. This argument can be given multiple times. If it is not set, only the own public key will be trusted. See *SECURITY* for more info. -*--genkey*:: - Generate and print a random key pair and exit. The key pair is printed as - base62 and can be used as private-key, public-key and trusted-key options. - *--algo *, *--algorithm *:: Supported encryption algorithms ("plain", "aes128", "aes256", or "chacha20"). Nodes exchange the supported algorithms and select the one that is fastest on @@ -178,10 +177,12 @@ vpncloud - Peer-to-peer VPN *--statsd-server *:: If set, periodically send statistics on current traffic and some important - events to the given statsd server (host:port). + events to the given statsd server (host:port). + Please see *STATSD SUPPORT* for more info. *--statsd-prefix *:: Sets the prefix to use for all statsd entries. [default: **vpncloud**] + Please see *STATSD SUPPORT* for more info. *--daemon*:: Spawn a background process instead of running the process in the foreground. @@ -196,6 +197,12 @@ vpncloud - Peer-to-peer VPN Disable automatic port forward. If this option is not set, VpnCloud tries to detect a NAT router and automatically add a port forwarding to it. +*--hook