Compare commits

..

No commits in common. "ed260d9a986cc55850c4ebdf57d42c80928d554e" and "4c934ea028fb2607b1350e46293b9ff2d54ba2c3" have entirely different histories.

12 changed files with 156 additions and 515 deletions

View File

@ -4,9 +4,8 @@ This project follows [semantic versioning](http://semver.org).
### UNRELEASED ### UNRELEASED
- [added] Support for websocket proxy mode
- [added] Support for hook scripts to handle certain situations
- [added] Support for creating shell completions - [added] Support for creating shell completions
- [added] Support for hook scripts to handle certain situations
- [removed] Removed dummy device type - [removed] Removed dummy device type
- [changed] Updated dependencies - [changed] Updated dependencies
- [changed] Changed Rust version to 1.49.0 - [changed] Changed Rust version to 1.49.0

115
Cargo.lock generated
View File

@ -44,27 +44,12 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 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]] [[package]]
name = "boxfnonce" name = "boxfnonce"
version = "0.1.1" version = "0.1.1"
@ -85,9 +70,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.6.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -149,12 +134,6 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.3.4" version = "0.3.4"
@ -269,15 +248,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "discard" name = "discard"
version = "1.0.4" version = "1.0.4"
@ -312,16 +282,6 @@ dependencies = [
"percent-encoding", "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]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.2" version = "0.2.2"
@ -368,12 +328,6 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "httparse"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.0" version = "0.2.0"
@ -398,15 +352,6 @@ dependencies = [
"xmltree", "xmltree",
] ]
[[package]]
name = "input_buffer"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
dependencies = [
"bytes",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.9.0" version = "0.9.0"
@ -544,12 +489,6 @@ version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -868,19 +807,6 @@ dependencies = [
"yaml-rust", "yaml-rust",
] ]
[[package]]
name = "sha-1"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4b312c3731e3fe78a185e6b9b911a7aa715b8e31cce117975219aab2acf285d"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpuid-bool",
"digest",
"opaque-debug",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.6.0" version = "0.6.0"
@ -1114,31 +1040,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tungstenite"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24"
dependencies = [
"base64",
"byteorder",
"bytes",
"http",
"httparse",
"input_buffer",
"log",
"rand",
"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]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.4" version = "0.3.4"
@ -1193,12 +1094,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf-8"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"
@ -1239,8 +1134,6 @@ dependencies = [
"tempfile", "tempfile",
"thiserror", "thiserror",
"time", "time",
"tungstenite",
"url",
"yaml-rust", "yaml-rust",
] ]
@ -1257,9 +1150,9 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.2+wasi-snapshot-preview1" version = "0.10.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"

View File

@ -29,17 +29,15 @@ privdrop = "0.5"
byteorder = "1.4" byteorder = "1.4"
thiserror = "1.0" thiserror = "1.0"
smallvec = "1.6" smallvec = "1.6"
tungstenite = { version = "0.12", optional = true, default-features = false }
url = { version = "2.2", optional = true }
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"
criterion = "0.3" criterion = "0.3"
[features] [features]
default = ["nat", "websocket"] default = ["nat"]
bench = []
nat = ["igd"] nat = ["igd"]
websocket = ["tungstenite", "url"]
[[bench]] [[bench]]
name = "bench" name = "bench"

View File

@ -8,7 +8,7 @@ from datetime import date
# Note: this script will run for ~8 minutes and incur costs of about $ 0.02 # Note: this script will run for ~8 minutes and incur costs of about $ 0.02
FILE = "../target/release/vpncloud" FILE = "../target/release/vpncloud"
VERSION = "2.0.1" VERSION = "2.0.0-alpha1"
REGION = "eu-central-1" REGION = "eu-central-1"
env = EC2Environment( env = EC2Environment(

View File

@ -5,7 +5,7 @@ import atexit, argparse, os
REGION = "eu-central-1" REGION = "eu-central-1"
VERSION = "2.0.1" VERSION = "2.0.0"
parser = argparse.ArgumentParser(description='Create a test setup') parser = argparse.ArgumentParser(description='Create a test setup')
@ -25,22 +25,15 @@ if args.keyname:
with open(args.keyfile, 'r') as fp: with open(args.keyfile, 'r') as fp:
privatekey = fp.read() 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( setup = EC2Environment(
region = REGION, region = REGION,
node_count = args.count, node_count = args.count,
instance_type = args.instancetype, instance_type = args.instancetype,
vpncloud_version = args.version,
cluster_nodes = args.cluster, cluster_nodes = args.cluster,
subnet = args.subnet or CREATE, subnet = args.subnet or CREATE,
keyname = args.keyname or CREATE, keyname = args.keyname or CREATE,
privatekey = privatekey, privatekey = privatekey
**opts
) )
if not args.keyname: if not args.keyname:

View File

@ -99,7 +99,11 @@ 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> { impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new(config: &Config, socket: S, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self { 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)
};
let (learning, broadcast) = match config.mode { let (learning, broadcast) = match config.mode {
Mode::Normal => { Mode::Normal => {
match config.device_type { match config.device_type {
@ -227,7 +231,6 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
self.own_addresses.push(pfw.get_internal_ip().into()); self.own_addresses.push(pfw.get_internal_ip().into());
self.own_addresses.push(pfw.get_external_ip().into()); self.own_addresses.push(pfw.get_external_ip().into());
} }
debug!("Own addresses: {:?}", self.own_addresses);
// TODO: detect address changes and call event // TODO: detect address changes and call event
Ok(()) Ok(())
} }
@ -914,7 +917,7 @@ impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS
/// Also, this method will call `housekeep` every second. /// Also, this method will call `housekeep` every second.
pub fn run(&mut self) { pub fn run(&mut self) {
let ctrlc = CtrlC::new(); let ctrlc = CtrlC::new();
let waiter = try_fail!(WaitImpl::new(self.socket.as_raw_fd(), self.device.as_raw_fd(), 1000), "Failed to setup poll: {}"); let waiter = try_fail!(WaitImpl::new(&self.socket, &self.device, 1000), "Failed to setup poll: {}");
let mut buffer = MsgBuffer::new(SPACE_BEFORE); let mut buffer = MsgBuffer::new(SPACE_BEFORE);
let mut poll_error = false; let mut poll_error = false;
self.config.call_hook("vpn_started", vec![("IFNAME", self.device.ifname())], true); self.config.call_hook("vpn_started", vec![("IFNAME", self.device.ifname())], true);

View File

@ -2,14 +2,15 @@
// Copyright (C) 2015-2020 Dennis Schwerdel // Copyright (C) 2015-2020 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md) // This software is licensed under GPL-3 or newer (see LICENSE.md)
use super::{device::Type, types::Mode, util::Duration, util::run_cmd}; use super::{device::Type, types::Mode, util::Duration};
pub use crate::crypto::Config as CryptoConfig; pub use crate::crypto::Config as CryptoConfig;
use crate::util::run_cmd;
use std::{ use std::{
cmp::max, cmp::max,
collections::HashMap, collections::HashMap,
ffi::OsStr, ffi::OsStr,
process, net::{IpAddr, Ipv6Addr, SocketAddr},
process::Command,
thread thread
}; };
use structopt::{clap::Shell, StructOpt}; use structopt::{clap::Shell, StructOpt};
@ -17,6 +18,17 @@ use structopt::{clap::Shell, StructOpt};
pub const DEFAULT_PEER_TIMEOUT: u16 = 300; pub const DEFAULT_PEER_TIMEOUT: u16 = 300;
pub const DEFAULT_PORT: u16 = 3210; 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)] #[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct Config { pub struct Config {
@ -31,7 +43,7 @@ pub struct Config {
pub crypto: CryptoConfig, pub crypto: CryptoConfig,
pub listen: String, pub listen: SocketAddr,
pub peers: Vec<String>, pub peers: Vec<String>,
pub peer_timeout: Duration, pub peer_timeout: Duration,
pub keepalive: Option<Duration>, pub keepalive: Option<Duration>,
@ -66,7 +78,7 @@ impl Default for Config {
ifup: None, ifup: None,
ifdown: None, ifdown: None,
crypto: CryptoConfig::default(), crypto: CryptoConfig::default(),
listen: "3210".to_string(), listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
peers: vec![], peers: vec![],
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration, peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
keepalive: None, keepalive: None,
@ -119,7 +131,7 @@ impl Config {
self.ifdown = Some(val); self.ifdown = Some(val);
} }
if let Some(val) = file.listen { if let Some(val) = file.listen {
self.listen = val; self.listen = parse_listen(&val);
} }
if let Some(mut val) = file.peers { if let Some(mut val) = file.peers {
self.peers.append(&mut val); self.peers.append(&mut val);
@ -223,7 +235,7 @@ impl Config {
self.ifdown = Some(val); self.ifdown = Some(val);
} }
if let Some(val) = args.listen { if let Some(val) = args.listen {
self.listen = val; self.listen = parse_listen(&val);
} }
self.peers.append(&mut args.peers); self.peers.append(&mut args.peers);
if let Some(val) = args.peer_timeout { if let Some(val) = args.peer_timeout {
@ -306,7 +318,7 @@ impl Config {
pub fn get_keepalive(&self) -> Duration { pub fn get_keepalive(&self) -> Duration {
match self.keepalive { match self.keepalive {
Some(dur) => dur, Some(dur) => dur,
None => max(self.peer_timeout / 2 - 60, 1), None => max(self.peer_timeout / 2 - 60, 1)
} }
} }
@ -324,7 +336,7 @@ impl Config {
return return
} }
let script = script.unwrap(); let script = script.unwrap();
let mut cmd = process::Command::new("sh"); let mut cmd = Command::new("sh");
cmd.arg("-c").arg(script).envs(envs).env("EVENT", event); cmd.arg("-c").arg(script).envs(envs).env("EVENT", event);
debug!("Running event script: {:?}", cmd); debug!("Running event script: {:?}", cmd);
if detach { if detach {
@ -358,7 +370,7 @@ pub struct Args {
pub mode: Option<Mode>, pub mode: Option<Mode>,
/// The shared password to encrypt all traffic /// The shared password to encrypt all traffic
#[structopt(short, long, env)] #[structopt(short, long, required_unless_one = &["private-key", "config", "genkey", "version", "completion"], env)]
pub password: Option<String>, pub password: Option<String>,
/// The private key to use /// The private key to use
@ -449,6 +461,10 @@ pub struct Args {
#[structopt(long)] #[structopt(long)]
pub version: bool, pub version: bool,
/// Generate and print a key-pair and exit
#[structopt(long, conflicts_with = "private_key")]
pub genkey: bool,
/// Disable automatic port forwarding /// Disable automatic port forwarding
#[structopt(long)] #[structopt(long)]
pub no_port_forwarding: bool, pub no_port_forwarding: bool,
@ -485,43 +501,17 @@ pub struct Args {
#[structopt(long)] #[structopt(long)]
pub log_file: Option<String>, pub log_file: Option<String>,
/// Call script on event
#[structopt(long)]
pub hook: Vec<String>,
#[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,
/// Run a websocket proxy
#[cfg(feature = "websocket")]
#[structopt(alias = "wsproxy")]
WsProxy {
/// Websocket listen address IP:PORT
#[structopt(long, short, default_value="3210")]
listen: String
},
/// Migrate an old config file /// Migrate an old config file
#[structopt(alias = "migrate")] #[structopt(long, alias = "migrate", requires = "config")]
MigrateConfig { pub migrate_config: bool,
/// Config file
#[structopt(long)]
config_file: String,
},
/// Generate shell completions /// Generate shell completions
Completion { #[structopt(long)]
/// Shell to create completions for pub completion: Option<Shell>,
#[structopt(long)]
shell: Shell /// Call script on event
} #[structopt(long)]
pub hook: Vec<String>
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -531,7 +521,7 @@ pub struct ConfigFileDevice {
pub type_: Option<Type>, pub type_: Option<Type>,
pub name: Option<String>, pub name: Option<String>,
pub path: Option<String>, pub path: Option<String>,
pub fix_rp_filter: Option<bool>, pub fix_rp_filter: Option<bool>
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -540,14 +530,14 @@ pub struct ConfigFileBeacon {
pub store: Option<String>, pub store: Option<String>,
pub load: Option<String>, pub load: Option<String>,
pub interval: Option<Duration>, pub interval: Option<Duration>,
pub password: Option<String>, pub password: Option<String>
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)] #[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
pub struct ConfigFileStatsd { pub struct ConfigFileStatsd {
pub server: Option<String>, pub server: Option<String>,
pub prefix: Option<String>, pub prefix: Option<String>
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -613,46 +603,43 @@ statsd:
server: example.com:1234 server: example.com:1234
prefix: prefix prefix: prefix
"; ";
assert_eq!( assert_eq!(serde_yaml::from_str::<ConfigFile>(config_file).unwrap(), ConfigFile {
serde_yaml::from_str::<ConfigFile>(config_file).unwrap(), device: Some(ConfigFileDevice {
ConfigFile { type_: Some(Type::Tun),
device: Some(ConfigFileDevice { name: Some("vpncloud%d".to_string()),
type_: Some(Type::Tun), path: Some("/dev/net/tun".to_string()),
name: Some("vpncloud%d".to_string()), fix_rp_filter: None
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()),
ip: Some("10.0.1.1/16".to_string()), ifdown: Some("true".to_string()),
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()), crypto: CryptoConfig::default(),
ifdown: Some("true".to_string()), listen: None,
crypto: CryptoConfig::default(), peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
listen: None, peer_timeout: Some(600),
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]), keepalive: Some(840),
peer_timeout: Some(600), beacon: Some(ConfigFileBeacon {
keepalive: Some(840), store: Some("/run/vpncloud.beacon.out".to_string()),
beacon: Some(ConfigFileBeacon { load: Some("/run/vpncloud.beacon.in".to_string()),
store: Some("/run/vpncloud.beacon.out".to_string()), interval: Some(3600),
load: Some("/run/vpncloud.beacon.in".to_string()), password: Some("test123".to_string())
interval: Some(3600), }),
password: Some("test123".to_string()) mode: Some(Mode::Normal),
}), switch_timeout: Some(300),
mode: Some(Mode::Normal), claims: Some(vec!["10.0.1.0/24".to_string()]),
switch_timeout: Some(300), auto_claim: None,
claims: Some(vec!["10.0.1.0/24".to_string()]), port_forwarding: Some(true),
auto_claim: None, user: Some("nobody".to_string()),
port_forwarding: Some(true), group: Some("nogroup".to_string()),
user: Some("nobody".to_string()), pid_file: Some("/run/vpncloud.run".to_string()),
group: Some("nogroup".to_string()), stats_file: Some("/var/log/vpncloud.stats".to_string()),
pid_file: Some("/run/vpncloud.run".to_string()), statsd: Some(ConfigFileStatsd {
stats_file: Some("/var/log/vpncloud.stats".to_string()), server: Some("example.com:1234".to_string()),
statsd: Some(ConfigFileStatsd { prefix: Some("prefix".to_string())
server: Some("example.com:1234".to_string()), }),
prefix: Some("prefix".to_string()) hook: None,
}), hooks: HashMap::new()
hook: None, })
hooks: HashMap::new()
}
)
} }
#[test] #[test]
@ -666,7 +653,7 @@ fn default_config_as_default() {
ifup: None, ifup: None,
ifdown: None, ifdown: None,
crypto: CryptoConfig::default(), crypto: CryptoConfig::default(),
listen: "[::]:3210".to_string(), listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
peers: vec![], peers: vec![],
peer_timeout: 0, peer_timeout: 0,
keepalive: None, keepalive: None,
@ -703,7 +690,7 @@ fn config_merge() {
type_: Some(Type::Tun), type_: Some(Type::Tun),
name: Some("vpncloud%d".to_string()), name: Some("vpncloud%d".to_string()),
path: None, path: None,
fix_rp_filter: None, fix_rp_filter: None
}), }),
ip: None, ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()), ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
@ -717,7 +704,7 @@ fn config_merge() {
store: Some("/run/vpncloud.beacon.out".to_string()), store: Some("/run/vpncloud.beacon.out".to_string()),
load: Some("/run/vpncloud.beacon.in".to_string()), load: Some("/run/vpncloud.beacon.in".to_string()),
interval: Some(7200), interval: Some(7200),
password: Some("test123".to_string()), password: Some("test123".to_string())
}), }),
mode: Some(Mode::Normal), mode: Some(Mode::Normal),
switch_timeout: Some(300), switch_timeout: Some(300),
@ -730,7 +717,7 @@ fn config_merge() {
stats_file: Some("/var/log/vpncloud.stats".to_string()), stats_file: Some("/var/log/vpncloud.stats".to_string()),
statsd: Some(ConfigFileStatsd { statsd: Some(ConfigFileStatsd {
server: Some("example.com:1234".to_string()), server: Some("example.com:1234".to_string()),
prefix: Some("prefix".to_string()), prefix: Some("prefix".to_string())
}), }),
hook: None, hook: None,
hooks: HashMap::new() hooks: HashMap::new()
@ -742,7 +729,7 @@ fn config_merge() {
ip: None, ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()), ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()), ifdown: Some("true".to_string()),
listen: "3210".to_string(), listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()], peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600, peer_timeout: 600,
keepalive: Some(840), keepalive: Some(840),
@ -769,7 +756,7 @@ fn config_merge() {
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()), ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
ifdown: Some("ifconfig $IFNAME down".to_string()), ifdown: Some("ifconfig $IFNAME down".to_string()),
password: Some("anothersecret".to_string()), password: Some("anothersecret".to_string()),
listen: Some("[::]:3211".to_string()), listen: Some("3211".to_string()),
peer_timeout: Some(1801), peer_timeout: Some(1801),
keepalive: Some(850), keepalive: Some(850),
switch_timeout: Some(301), switch_timeout: Some(301),
@ -799,7 +786,7 @@ fn config_merge() {
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()), ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
ifdown: Some("ifconfig $IFNAME down".to_string()), ifdown: Some("ifconfig $IFNAME down".to_string()),
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() }, crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
listen: "[::]:3211".to_string(), listen: "[::]:3211".parse::<SocketAddr>().unwrap(),
peers: vec![ peers: vec![
"remote.machine.foo:3210".to_string(), "remote.machine.foo:3210".to_string(),
"remote.machine.bar:3210".to_string(), "remote.machine.bar:3210".to_string(),

View File

@ -27,7 +27,6 @@ pub mod port_forwarding;
pub mod table; pub mod table;
pub mod traffic; pub mod traffic;
pub mod types; pub mod types;
#[cfg(feature = "websocket")] pub mod wsproxy;
use structopt::StructOpt; use structopt::StructOpt;
@ -37,7 +36,7 @@ use std::{
net::{Ipv4Addr, UdpSocket}, net::{Ipv4Addr, UdpSocket},
os::unix::fs::PermissionsExt, os::unix::fs::PermissionsExt,
path::Path, path::Path,
process, process::Command,
str::FromStr, str::FromStr,
sync::Mutex, sync::Mutex,
thread thread
@ -45,17 +44,15 @@ use std::{
use crate::{ use crate::{
cloud::GenericCloud, cloud::GenericCloud,
config::{Args, Command, Config}, config::{Args, Config},
crypto::Crypto, crypto::Crypto,
device::{Device, TunTapDevice, Type}, device::{Device, TunTapDevice, Type},
net::Socket,
oldconfig::OldConfigFile, oldconfig::OldConfigFile,
payload::Protocol, payload::Protocol,
util::SystemTimeSource, port_forwarding::PortForwarding,
util::SystemTimeSource
}; };
#[cfg(feature = "websocket")]
use crate::wsproxy::ProxyConnection;
struct DualLogger { struct DualLogger {
file: Option<Mutex<File>> file: Option<Mutex<File>>
@ -105,7 +102,7 @@ impl log::Log for DualLogger {
} }
fn run_script(script: &str, ifname: &str) { fn run_script(script: &str, ifname: &str) {
let mut cmd = process::Command::new("sh"); let mut cmd = Command::new("sh");
cmd.arg("-c").arg(&script).env("IFNAME", ifname); cmd.arg("-c").arg(&script).env("IFNAME", ifname);
debug!("Running script: {:?}", cmd); debug!("Running script: {:?}", cmd);
match cmd.status() { match cmd.status() {
@ -164,10 +161,11 @@ fn setup_device(config: &Config) -> TunTapDevice {
device device
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn run<P: Protocol, S: Socket>(config: Config, socket: S) { fn run<P: Protocol>(config: Config) {
let device = setup_device(&config); let device = setup_device(&config);
let port_forwarding = if config.port_forwarding { socket.create_port_forwarding() } else { None }; let port_forwarding = if config.port_forwarding { PortForwarding::new(config.listen.port()) } else { None };
let stats_file = match config.stats_file { let stats_file = match config.stats_file {
None => None, None => None,
Some(ref name) => { Some(ref name) => {
@ -184,7 +182,7 @@ fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
} }
}; };
let mut cloud = let mut cloud =
GenericCloud::<TunTapDevice, P, S, SystemTimeSource>::new(&config, socket, device, port_forwarding, stats_file); GenericCloud::<TunTapDevice, P, UdpSocket, SystemTimeSource>::new(&config, device, port_forwarding, stats_file);
for addr in config.peers { for addr in config.peers {
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr); try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
cloud.add_reconnect_peer(addr); cloud.add_reconnect_peer(addr);
@ -227,6 +225,18 @@ fn main() {
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION")); println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
return 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
}
if let Some(shell) = args.completion {
Args::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut io::stdout());
return
}
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}"); let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
log::set_boxed_logger(Box::new(logger)).unwrap(); log::set_boxed_logger(Box::new(logger)).unwrap();
assert!(!args.verbose || !args.quiet); assert!(!args.verbose || !args.quiet);
@ -237,44 +247,23 @@ fn main() {
} else { } else {
log::LevelFilter::Info log::LevelFilter::Info
}); });
if let Some(cmd) = args.cmd { if args.migrate_config {
match cmd { let file = args.config.unwrap();
Command::GenKey => { info!("Trying to convert from old config format");
let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref()); let f = try_fail!(File::open(&file), "Failed to open config file: {:?}");
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey); let config_file_old: OldConfigFile =
println!( try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust." let new_config = config_file_old.convert();
); info!("Successfully converted from old format");
} info!("Renaming original file to {}.orig", file);
Command::MigrateConfig { config_file } => { try_fail!(fs::rename(&file, format!("{}.orig", file)), "Failed to rename original file: {:?}");
info!("Trying to convert from old config format"); info!("Writing new config back into {}", file);
let f = try_fail!(File::open(&config_file), "Failed to open config file: {:?}"); let f = try_fail!(File::create(&file), "Failed to open config file: {:?}");
let config_file_old: OldConfigFile = try_fail!(
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}"); fs::set_permissions(&file, fs::Permissions::from_mode(0o600)),
let new_config = config_file_old.convert(); "Failed to set permissions on file: {:?}"
info!("Successfully converted from old format"); );
info!("Renaming original file to {}.orig", config_file); try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
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::Completion { shell } => {
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: {:?}");
}
}
return return
} }
let mut config = Config::default(); let mut config = Config::default();
@ -298,22 +287,8 @@ fn main() {
} }
config.merge_args(args); config.merge_args(args);
debug!("Config: {:?}", config); debug!("Config: {:?}", 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
}
#[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::<payload::Frame, _>(config, socket),
Type::Tun => run::<payload::Packet, _>(config, socket)
}
return
}
let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
match config.device_type { match config.device_type {
Type::Tap => run::<payload::Frame, _>(config, socket), Type::Tap => run::<payload::Frame>(config),
Type::Tun => run::<payload::Packet, _>(config, socket) Type::Tun => run::<payload::Packet>(config)
} }
} }

View File

@ -5,13 +5,12 @@
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
io::{self, ErrorKind}, io::{self, ErrorKind},
net::{IpAddr, SocketAddr, UdpSocket, Ipv6Addr}, net::{IpAddr, SocketAddr, UdpSocket},
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
sync::atomic::{AtomicBool, Ordering} sync::atomic::{AtomicBool, Ordering}
}; };
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource}; use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
use crate::port_forwarding::PortForwarding;
pub fn mapped_addr(addr: SocketAddr) -> SocketAddr { pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
// HOT PATH // HOT PATH
@ -21,35 +20,16 @@ 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 { pub trait Socket: AsRawFd + Sized {
fn listen(addr: &str) -> Result<Self, io::Error>; fn listen(addr: SocketAddr) -> Result<Self, io::Error>;
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, 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 send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>;
fn address(&self) -> Result<SocketAddr, io::Error>; fn address(&self) -> Result<SocketAddr, io::Error>;
fn create_port_forwarding(&self) -> Option<PortForwarding>;
}
pub 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 { impl Socket for UdpSocket {
fn listen(addr: &str) -> Result<Self, io::Error> { fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
let addr = parse_listen(addr);
UdpSocket::bind(addr) UdpSocket::bind(addr)
} }
@ -65,13 +45,7 @@ impl Socket for UdpSocket {
} }
fn address(&self) -> Result<SocketAddr, io::Error> { fn address(&self) -> Result<SocketAddr, io::Error> {
let mut addr = self.local_addr()?; self.local_addr()
addr.set_ip(get_ip());
Ok(addr)
}
fn create_port_forwarding(&self) -> Option<PortForwarding> {
PortForwarding::new(self.address().unwrap().port())
} }
} }
@ -133,8 +107,8 @@ impl AsRawFd for MockSocket {
} }
impl Socket for MockSocket { impl Socket for MockSocket {
fn listen(addr: &str) -> Result<Self, io::Error> { fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
Ok(Self::new(parse_listen(addr))) Ok(Self::new(addr))
} }
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> { fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
@ -159,23 +133,4 @@ impl Socket for MockSocket {
fn address(&self) -> Result<SocketAddr, io::Error> { fn address(&self) -> Result<SocketAddr, io::Error> {
Ok(self.address) Ok(self.address)
} }
fn create_port_forwarding(&self) -> Option<PortForwarding> {
None
}
}
#[cfg(feature = "bench")]
mod bench {
use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket};
use test::Bencher;
#[bench]
fn udp_send(b: &mut Bencher) {
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
let data = [0; 1400];
let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1);
b.iter(|| sock.send_to(&data, &addr).unwrap());
b.bytes = 1400;
}
} }

View File

@ -2,9 +2,11 @@
// Copyright (C) 2015-2020 Dennis Schwerdel // Copyright (C) 2015-2020 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md) // This software is licensed under GPL-3 or newer (see LICENSE.md)
use crate::device::Device;
use std::{io, os::unix::io::RawFd}; use std::{io, os::unix::io::RawFd};
use super::WaitResult; use super::WaitResult;
use crate::net::Socket;
pub struct EpollWait { pub struct EpollWait {
poll_fd: RawFd, poll_fd: RawFd,
@ -15,21 +17,21 @@ pub struct EpollWait {
} }
impl EpollWait { impl EpollWait {
pub fn new(socket: RawFd, device: RawFd, timeout: u32) -> io::Result<Self> { pub fn new<S: Socket>(socket: &S, device: &dyn Device, timeout: u32) -> io::Result<Self> {
Self::create(socket, device, timeout, libc::EPOLLIN as u32) Self::create(socket, device, timeout, libc::EPOLLIN as u32)
} }
pub fn testing(socket: RawFd, device: RawFd, timeout: u32) -> io::Result<Self> { pub fn testing<S: Socket>(socket: &S, device: &dyn Device, timeout: u32) -> io::Result<Self> {
Self::create(socket, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32) Self::create(socket, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32)
} }
fn create(socket: RawFd, device: RawFd, timeout: u32, flags: u32) -> io::Result<Self> { fn create<S: Socket>(socket: &S, device: &dyn Device, timeout: u32, flags: u32) -> io::Result<Self> {
let mut event = libc::epoll_event { u64: 0, events: 0 }; let mut event = libc::epoll_event { u64: 0, events: 0 };
let poll_fd = unsafe { libc::epoll_create(3) }; let poll_fd = unsafe { libc::epoll_create(3) };
if poll_fd == -1 { if poll_fd == -1 {
return Err(io::Error::last_os_error()) return Err(io::Error::last_os_error())
} }
for fd in &[socket, device] { for fd in &[socket.as_raw_fd(), device.as_raw_fd()] {
event.u64 = *fd as u64; event.u64 = *fd as u64;
event.events = flags; event.events = flags;
let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) }; let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) };
@ -37,7 +39,7 @@ impl EpollWait {
return Err(io::Error::last_os_error()) return Err(io::Error::last_os_error())
} }
} }
Ok(Self { poll_fd, event, socket, device, timeout }) Ok(Self { poll_fd, event, socket: socket.as_raw_fd(), device: device.as_raw_fd(), timeout })
} }
} }

View File

@ -9,7 +9,7 @@ mod peers;
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
io::Write, io::Write,
net::SocketAddr, net::{IpAddr, Ipv6Addr, SocketAddr},
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Once Once
@ -89,17 +89,16 @@ impl<P: Protocol> Simulator<P> {
pub fn add_node(&mut self, nat: bool, config: &Config) -> SocketAddr { pub fn add_node(&mut self, nat: bool, config: &Config) -> SocketAddr {
let mut config = config.clone(); let mut config = config.clone();
MockSocket::set_nat(nat); MockSocket::set_nat(nat);
config.listen = format!("[::]:{}", self.next_port); config.listen = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.next_port);
let addr = config.listen.parse::<SocketAddr>().unwrap();
if config.crypto.password.is_none() && config.crypto.private_key.is_none() { if config.crypto.password.is_none() && config.crypto.private_key.is_none() {
config.crypto.password = Some("test123".to_string()) config.crypto.password = Some("test123".to_string())
} }
DebugLogger::set_node(self.next_port as usize); DebugLogger::set_node(self.next_port as usize);
self.next_port += 1; self.next_port += 1;
let node = TestNode::new(&config, MockSocket::new(addr), MockDevice::new(), None, None); let node = TestNode::new(&config, MockDevice::new(), None, None);
DebugLogger::set_node(0); DebugLogger::set_node(0);
self.nodes.insert(addr, node); self.nodes.insert(config.listen, node);
addr config.listen
} }
#[allow(dead_code)] #[allow(dead_code)]

View File

@ -1,163 +0,0 @@
use super::{
net::{get_ip, mapped_addr, parse_listen, Socket},
poll::{WaitImpl, WaitResult},
port_forwarding::PortForwarding,
util::MsgBuffer
};
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, Message};
use url::Url;
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<W: Write>(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::<NetworkEndian>(addr.port())?;
}
_ => unreachable!()
}
Ok(())
}
fn read_addr<R: Read>(mut r: R) -> Result<SocketAddr, io::Error> {
let mut ip = [0u8; 16];
r.read_exact(&mut ip)?;
let port = r.read_u16::<NetworkEndian>()?;
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);
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);
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 * 1000)?;
let mut buffer = [0; 65535];
for evt in poll {
match evt {
WaitResult::Socket => {
let msg = io_error!(websocket.read_message(), "Failed to read message on websocket {}: {}", peer)?;
match msg {
Message::Binary(data) => {
let dst = read_addr(Cursor::new(&data))?;
udpsocket.send_to(&data[18..], dst)?;
}
Message::Close(_) => return Ok(()),
_ => {}
}
}
WaitResult::Device => {
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 => {
io_error!(websocket.write_message(Message::Ping(vec![])), "Failed to send ping: {}")?;
}
WaitResult::Error(err) => return Err(err)
}
}
Ok(())
}
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()?;
spawn(move || {
if let Err(err) = serve_proxy_connection(stream) {
error!("Error on connection {}: {}", peer, err);
}
});
}
Ok(())
}
pub struct ProxyConnection {
addr: SocketAddr,
socket: WebSocket<AutoStream>
}
impl ProxyConnection {
fn read_message(&mut self) -> Result<Vec<u8>, 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 {
self.socket.get_ref().as_raw_fd()
}
}
impl Socket for ProxyConnection {
fn listen(url: &str) -> Result<Self, io::Error> {
let parsed_url = io_error!(Url::parse(url), "Invalid 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::<SocketAddr>().unwrap();
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<SocketAddr, io::Error> {
buffer.clear();
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<usize, io::Error> {
let mut msg = Vec::with_capacity(data.len() + 18);
write_addr(addr, &mut msg)?;
msg.write_all(data)?;
io_error!(self.socket.write_message(Message::Binary(msg)), "Failed to write to ws proxy: {}")?;
Ok(data.len())
}
fn address(&self) -> Result<SocketAddr, io::Error> {
Ok(self.addr)
}
fn create_port_forwarding(&self) -> Option<PortForwarding> {
None
}
}