mirror of https://github.com/dswd/vpncloud.git
Compare commits
11 Commits
4c934ea028
...
ed260d9a98
Author | SHA1 | Date |
---|---|---|
Dennis Schwerdel | ed260d9a98 | |
Dennis Schwerdel | d154f85ecd | |
Dennis Schwerdel | 65eef143cd | |
Dennis Schwerdel | 791ecfb0fe | |
Dennis Schwerdel | e9122743e9 | |
Dennis Schwerdel | bd0d102358 | |
Dennis Schwerdel | a113b3ba22 | |
Dennis Schwerdel | f0d9ad2ccd | |
Dennis Schwerdel | e3fa631ed9 | |
Dennis Schwerdel | 124f7cbff9 | |
Dennis Schwerdel | d50490ac51 |
|
@ -4,8 +4,9 @@ This project follows [semantic versioning](http://semver.org).
|
||||||
|
|
||||||
### UNRELEASED
|
### 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 hook scripts to handle certain situations
|
||||||
|
- [added] Support for creating shell completions
|
||||||
- [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
|
||||||
|
|
|
@ -44,12 +44,27 @@ 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"
|
||||||
|
@ -70,9 +85,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.5.0"
|
version = "3.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59"
|
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
|
@ -134,6 +149,12 @@ 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"
|
||||||
|
@ -248,6 +269,15 @@ 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"
|
||||||
|
@ -282,6 +312,16 @@ 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"
|
||||||
|
@ -328,6 +368,12 @@ 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"
|
||||||
|
@ -352,6 +398,15 @@ 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"
|
||||||
|
@ -489,6 +544,12 @@ 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"
|
||||||
|
@ -807,6 +868,19 @@ 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"
|
||||||
|
@ -1040,6 +1114,31 @@ 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"
|
||||||
|
@ -1094,6 +1193,12 @@ 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"
|
||||||
|
@ -1134,6 +1239,8 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
"tungstenite",
|
||||||
|
"url",
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1150,9 +1257,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.1+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
|
|
|
@ -29,15 +29,17 @@ 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"]
|
default = ["nat", "websocket"]
|
||||||
bench = []
|
|
||||||
nat = ["igd"]
|
nat = ["igd"]
|
||||||
|
websocket = ["tungstenite", "url"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench"
|
name = "bench"
|
||||||
|
|
|
@ -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.0-alpha1"
|
VERSION = "2.0.1"
|
||||||
REGION = "eu-central-1"
|
REGION = "eu-central-1"
|
||||||
|
|
||||||
env = EC2Environment(
|
env = EC2Environment(
|
||||||
|
|
|
@ -5,7 +5,7 @@ import atexit, argparse, os
|
||||||
|
|
||||||
REGION = "eu-central-1"
|
REGION = "eu-central-1"
|
||||||
|
|
||||||
VERSION = "2.0.0"
|
VERSION = "2.0.1"
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Create a test setup')
|
parser = argparse.ArgumentParser(description='Create a test setup')
|
||||||
|
@ -25,15 +25,22 @@ 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:
|
||||||
|
|
|
@ -99,11 +99,7 @@ pub struct GenericCloud<D: Device, P: Protocol, S: Socket, TS: TimeSource> {
|
||||||
|
|
||||||
impl<D: Device, P: Protocol, S: Socket, TS: TimeSource> GenericCloud<D, P, S, TS> {
|
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, device: D, port_forwarding: Option<PortForwarding>, stats_file: Option<File>) -> Self {
|
pub fn new(config: &Config, socket: S, 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 {
|
||||||
|
@ -231,6 +227,7 @@ 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(())
|
||||||
}
|
}
|
||||||
|
@ -917,7 +914,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, &self.device, 1000), "Failed to setup poll: {}");
|
let waiter = try_fail!(WaitImpl::new(self.socket.as_raw_fd(), self.device.as_raw_fd(), 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);
|
||||||
|
|
107
src/config.rs
107
src/config.rs
|
@ -2,15 +2,14 @@
|
||||||
// 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};
|
use super::{device::Type, types::Mode, util::Duration, util::run_cmd};
|
||||||
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,
|
||||||
net::{IpAddr, Ipv6Addr, SocketAddr},
|
process,
|
||||||
process::Command,
|
|
||||||
thread
|
thread
|
||||||
};
|
};
|
||||||
use structopt::{clap::Shell, StructOpt};
|
use structopt::{clap::Shell, StructOpt};
|
||||||
|
@ -18,17 +17,6 @@ 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 {
|
||||||
|
@ -43,7 +31,7 @@ pub struct Config {
|
||||||
|
|
||||||
pub crypto: CryptoConfig,
|
pub crypto: CryptoConfig,
|
||||||
|
|
||||||
pub listen: SocketAddr,
|
pub listen: String,
|
||||||
pub peers: Vec<String>,
|
pub peers: Vec<String>,
|
||||||
pub peer_timeout: Duration,
|
pub peer_timeout: Duration,
|
||||||
pub keepalive: Option<Duration>,
|
pub keepalive: Option<Duration>,
|
||||||
|
@ -78,7 +66,7 @@ impl Default for Config {
|
||||||
ifup: None,
|
ifup: None,
|
||||||
ifdown: None,
|
ifdown: None,
|
||||||
crypto: CryptoConfig::default(),
|
crypto: CryptoConfig::default(),
|
||||||
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
|
listen: "3210".to_string(),
|
||||||
peers: vec![],
|
peers: vec![],
|
||||||
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
|
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
|
||||||
keepalive: None,
|
keepalive: None,
|
||||||
|
@ -131,7 +119,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 = parse_listen(&val);
|
self.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);
|
||||||
|
@ -235,7 +223,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 = parse_listen(&val);
|
self.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 {
|
||||||
|
@ -318,7 +306,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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +324,7 @@ impl Config {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let script = script.unwrap();
|
let script = script.unwrap();
|
||||||
let mut cmd = Command::new("sh");
|
let mut cmd = process::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 {
|
||||||
|
@ -370,7 +358,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, required_unless_one = &["private-key", "config", "genkey", "version", "completion"], env)]
|
#[structopt(short, long, env)]
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
|
|
||||||
/// The private key to use
|
/// The private key to use
|
||||||
|
@ -461,10 +449,6 @@ 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,
|
||||||
|
@ -501,17 +485,43 @@ pub struct Args {
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub log_file: Option<String>,
|
pub log_file: Option<String>,
|
||||||
|
|
||||||
/// Migrate an old config file
|
|
||||||
#[structopt(long, alias = "migrate", requires = "config")]
|
|
||||||
pub migrate_config: bool,
|
|
||||||
|
|
||||||
/// Generate shell completions
|
|
||||||
#[structopt(long)]
|
|
||||||
pub completion: Option<Shell>,
|
|
||||||
|
|
||||||
/// Call script on event
|
/// Call script on event
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub hook: Vec<String>
|
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
|
||||||
|
#[structopt(alias = "migrate")]
|
||||||
|
MigrateConfig {
|
||||||
|
/// Config file
|
||||||
|
#[structopt(long)]
|
||||||
|
config_file: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Generate shell completions
|
||||||
|
Completion {
|
||||||
|
/// Shell to create completions for
|
||||||
|
#[structopt(long)]
|
||||||
|
shell: Shell
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||||
|
@ -521,7 +531,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)]
|
||||||
|
@ -530,14 +540,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)]
|
||||||
|
@ -603,7 +613,9 @@ statsd:
|
||||||
server: example.com:1234
|
server: example.com:1234
|
||||||
prefix: prefix
|
prefix: prefix
|
||||||
";
|
";
|
||||||
assert_eq!(serde_yaml::from_str::<ConfigFile>(config_file).unwrap(), ConfigFile {
|
assert_eq!(
|
||||||
|
serde_yaml::from_str::<ConfigFile>(config_file).unwrap(),
|
||||||
|
ConfigFile {
|
||||||
device: Some(ConfigFileDevice {
|
device: Some(ConfigFileDevice {
|
||||||
type_: Some(Type::Tun),
|
type_: Some(Type::Tun),
|
||||||
name: Some("vpncloud%d".to_string()),
|
name: Some("vpncloud%d".to_string()),
|
||||||
|
@ -639,7 +651,8 @@ statsd:
|
||||||
}),
|
}),
|
||||||
hook: None,
|
hook: None,
|
||||||
hooks: HashMap::new()
|
hooks: HashMap::new()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -653,7 +666,7 @@ fn default_config_as_default() {
|
||||||
ifup: None,
|
ifup: None,
|
||||||
ifdown: None,
|
ifdown: None,
|
||||||
crypto: CryptoConfig::default(),
|
crypto: CryptoConfig::default(),
|
||||||
listen: "[::]:3210".parse::<SocketAddr>().unwrap(),
|
listen: "[::]:3210".to_string(),
|
||||||
peers: vec![],
|
peers: vec![],
|
||||||
peer_timeout: 0,
|
peer_timeout: 0,
|
||||||
keepalive: None,
|
keepalive: None,
|
||||||
|
@ -690,7 +703,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()),
|
||||||
|
@ -704,7 +717,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),
|
||||||
|
@ -717,7 +730,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()
|
||||||
|
@ -729,7 +742,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".parse::<SocketAddr>().unwrap(),
|
listen: "3210".to_string(),
|
||||||
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),
|
||||||
|
@ -756,7 +769,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),
|
||||||
|
@ -786,7 +799,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".parse::<SocketAddr>().unwrap(),
|
listen: "[::]:3211".to_string(),
|
||||||
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(),
|
||||||
|
|
87
src/main.rs
87
src/main.rs
|
@ -27,6 +27,7 @@ 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;
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ use std::{
|
||||||
net::{Ipv4Addr, UdpSocket},
|
net::{Ipv4Addr, UdpSocket},
|
||||||
os::unix::fs::PermissionsExt,
|
os::unix::fs::PermissionsExt,
|
||||||
path::Path,
|
path::Path,
|
||||||
process::Command,
|
process,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
thread
|
thread
|
||||||
|
@ -44,15 +45,17 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cloud::GenericCloud,
|
cloud::GenericCloud,
|
||||||
config::{Args, Config},
|
config::{Args, Command, Config},
|
||||||
crypto::Crypto,
|
crypto::Crypto,
|
||||||
device::{Device, TunTapDevice, Type},
|
device::{Device, TunTapDevice, Type},
|
||||||
|
net::Socket,
|
||||||
oldconfig::OldConfigFile,
|
oldconfig::OldConfigFile,
|
||||||
payload::Protocol,
|
payload::Protocol,
|
||||||
port_forwarding::PortForwarding,
|
util::SystemTimeSource,
|
||||||
util::SystemTimeSource
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "websocket")]
|
||||||
|
use crate::wsproxy::ProxyConnection;
|
||||||
|
|
||||||
struct DualLogger {
|
struct DualLogger {
|
||||||
file: Option<Mutex<File>>
|
file: Option<Mutex<File>>
|
||||||
|
@ -102,7 +105,7 @@ impl log::Log for DualLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_script(script: &str, ifname: &str) {
|
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);
|
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
|
||||||
debug!("Running script: {:?}", cmd);
|
debug!("Running script: {:?}", cmd);
|
||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
|
@ -161,11 +164,10 @@ fn setup_device(config: &Config) -> TunTapDevice {
|
||||||
device
|
device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn run<P: Protocol>(config: Config) {
|
fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
|
||||||
let device = setup_device(&config);
|
let 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 {
|
let stats_file = match config.stats_file {
|
||||||
None => None,
|
None => None,
|
||||||
Some(ref name) => {
|
Some(ref name) => {
|
||||||
|
@ -182,7 +184,7 @@ fn run<P: Protocol>(config: Config) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut cloud =
|
let mut cloud =
|
||||||
GenericCloud::<TunTapDevice, P, UdpSocket, SystemTimeSource>::new(&config, device, port_forwarding, stats_file);
|
GenericCloud::<TunTapDevice, P, S, SystemTimeSource>::new(&config, socket, device, port_forwarding, stats_file);
|
||||||
for addr in config.peers {
|
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);
|
||||||
|
@ -225,18 +227,6 @@ 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);
|
||||||
|
@ -247,23 +237,44 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
log::LevelFilter::Info
|
log::LevelFilter::Info
|
||||||
});
|
});
|
||||||
if args.migrate_config {
|
if let Some(cmd) = args.cmd {
|
||||||
let file = args.config.unwrap();
|
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");
|
info!("Trying to convert from old config format");
|
||||||
let f = try_fail!(File::open(&file), "Failed to open config file: {:?}");
|
let f = try_fail!(File::open(&config_file), "Failed to open config file: {:?}");
|
||||||
let config_file_old: OldConfigFile =
|
let config_file_old: OldConfigFile =
|
||||||
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
|
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
|
||||||
let new_config = config_file_old.convert();
|
let new_config = config_file_old.convert();
|
||||||
info!("Successfully converted from old format");
|
info!("Successfully converted from old format");
|
||||||
info!("Renaming original file to {}.orig", file);
|
info!("Renaming original file to {}.orig", config_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!(
|
try_fail!(
|
||||||
fs::set_permissions(&file, fs::Permissions::from_mode(0o600)),
|
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: {:?}"
|
"Failed to set permissions on file: {:?}"
|
||||||
);
|
);
|
||||||
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
|
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();
|
||||||
|
@ -287,8 +298,22 @@ 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 {
|
match config.device_type {
|
||||||
Type::Tap => run::<payload::Frame>(config),
|
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||||
Type::Tun => run::<payload::Packet>(config)
|
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 {
|
||||||
|
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||||
|
Type::Tun => run::<payload::Packet, _>(config, socket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
57
src/net.rs
57
src/net.rs
|
@ -5,12 +5,13 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
io::{self, ErrorKind},
|
io::{self, ErrorKind},
|
||||||
net::{IpAddr, SocketAddr, UdpSocket},
|
net::{IpAddr, SocketAddr, UdpSocket, Ipv6Addr},
|
||||||
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
|
||||||
|
@ -20,16 +21,35 @@ 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: SocketAddr) -> Result<Self, io::Error>;
|
fn listen(addr: &str) -> Result<Self, io::Error>;
|
||||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error>;
|
fn 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: SocketAddr) -> Result<Self, io::Error> {
|
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||||
|
let addr = parse_listen(addr);
|
||||||
UdpSocket::bind(addr)
|
UdpSocket::bind(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +65,13 @@ impl Socket for UdpSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||||
self.local_addr()
|
let mut addr = self.local_addr()?;
|
||||||
|
addr.set_ip(get_ip());
|
||||||
|
Ok(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_port_forwarding(&self) -> Option<PortForwarding> {
|
||||||
|
PortForwarding::new(self.address().unwrap().port())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +133,8 @@ impl AsRawFd for MockSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Socket for MockSocket {
|
impl Socket for MockSocket {
|
||||||
fn listen(addr: SocketAddr) -> Result<Self, io::Error> {
|
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||||
Ok(Self::new(addr))
|
Ok(Self::new(parse_listen(addr)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||||
|
@ -133,4 +159,23 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,11 +2,9 @@
|
||||||
// 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,
|
||||||
|
@ -17,21 +15,21 @@ pub struct EpollWait {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EpollWait {
|
impl EpollWait {
|
||||||
pub fn new<S: Socket>(socket: &S, device: &dyn Device, timeout: u32) -> io::Result<Self> {
|
pub fn new(socket: RawFd, device: RawFd, 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<S: Socket>(socket: &S, device: &dyn Device, timeout: u32) -> io::Result<Self> {
|
pub fn testing(socket: RawFd, device: RawFd, 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<S: Socket>(socket: &S, device: &dyn Device, timeout: u32, flags: u32) -> io::Result<Self> {
|
fn create(socket: RawFd, device: RawFd, 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.as_raw_fd(), device.as_raw_fd()] {
|
for fd in &[socket, device] {
|
||||||
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) };
|
||||||
|
@ -39,7 +37,7 @@ impl EpollWait {
|
||||||
return Err(io::Error::last_os_error())
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ mod peers;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
io::Write,
|
io::Write,
|
||||||
net::{IpAddr, Ipv6Addr, SocketAddr},
|
net::SocketAddr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
Once
|
Once
|
||||||
|
@ -89,16 +89,17 @@ 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 = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), self.next_port);
|
config.listen = format!("[::]:{}", 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, MockDevice::new(), None, None);
|
let node = TestNode::new(&config, MockSocket::new(addr), MockDevice::new(), None, None);
|
||||||
DebugLogger::set_node(0);
|
DebugLogger::set_node(0);
|
||||||
self.nodes.insert(config.listen, node);
|
self.nodes.insert(addr, node);
|
||||||
config.listen
|
addr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue