Compare commits

..

No commits in common. "66bef5cd21e2fb7f0d6f33ee0eb2299829a2eeb4" and "a3d0daa0f6666413efb0b9cc64a852adc931c08f" have entirely different histories.

41 changed files with 469 additions and 621 deletions

View File

@ -1,18 +1,6 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/rust/.devcontainer/base.Dockerfile # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/rust/.devcontainer/base.Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/rust:1 FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends asciidoctor valgrind && apt-get -y install --no-install-recommends asciidoctor valgrind
RUN rm /etc/localtime && ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
RUN chown vscode: -R /usr/local/rustup /usr/local/cargo
USER vscode
RUN rustup default 1.51.0 \
&& rustup component add clippy rust-src rustfmt
RUN cargo install cargo-outdated cargo-cache \
&& cargo cache -a

View File

@ -18,9 +18,6 @@ jobs:
name: Test Suite name: Test Suite
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: szenius/set-timezone@v1.0
with:
timezoneLinux: Europe/Berlin
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:

View File

@ -28,7 +28,6 @@ mkdir -p %{buildroot}/lib/systemd/system
mkdir -p %{buildroot}/usr/share/man/man1 mkdir -p %{buildroot}/usr/share/man/man1
cp %{buildroot}/../../../../../assets/example.net.disabled %{buildroot}/etc/vpncloud/example.net.disabled cp %{buildroot}/../../../../../assets/example.net.disabled %{buildroot}/etc/vpncloud/example.net.disabled
cp %{buildroot}/../../../../../assets/vpncloud@.service %{buildroot}/lib/systemd/system/vpncloud@.service cp %{buildroot}/../../../../../assets/vpncloud@.service %{buildroot}/lib/systemd/system/vpncloud@.service
cp %{buildroot}/../../../../../assets/vpncloud.target %{buildroot}/lib/systemd/system/vpncloud.target
cp %{buildroot}/../../../../../assets/vpncloud-wsproxy.service %{buildroot}/lib/systemd/system/vpncloud-wsproxy.service cp %{buildroot}/../../../../../assets/vpncloud-wsproxy.service %{buildroot}/lib/systemd/system/vpncloud-wsproxy.service
cp %{buildroot}/../../../../../target/vpncloud.1.gz %{buildroot}/usr/share/man/man1/vpncloud.1.gz cp %{buildroot}/../../../../../target/vpncloud.1.gz %{buildroot}/usr/share/man/man1/vpncloud.1.gz
cp -a * %{buildroot} cp -a * %{buildroot}
@ -41,7 +40,6 @@ rm -rf %{buildroot}
/etc/vpncloud/example.net.disabled /etc/vpncloud/example.net.disabled
/usr/bin/vpncloud /usr/bin/vpncloud
/lib/systemd/system/vpncloud@.service /lib/systemd/system/vpncloud@.service
/lib/systemd/system/vpncloud.target
/lib/systemd/system/vpncloud-wsproxy.service /lib/systemd/system/vpncloud-wsproxy.service
/usr/share/man/man1/vpncloud.1.gz /usr/share/man/man1/vpncloud.1.gz

12
.whitesource Normal file
View File

@ -0,0 +1,12 @@
{
"scanSettings": {
"baseBranches": []
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure",
"displayMode": "diff"
},
"issueSettings": {
"minSeverityLevel": "LOW"
}
}

View File

@ -2,15 +2,14 @@
This project follows [semantic versioning](http://semver.org). This project follows [semantic versioning](http://semver.org).
### v2.2.0 (2021-04-06) ### UNRELEASED
- [added] Service target file (thanks to mnhauke)
- [added] Added interactive configuration wizard - [added] Added interactive configuration wizard
- [added] Support for (un-)installation - [added] Support for (un-)installation
- [added] Building static binaries - [added] Building static binaries
- [added] Building i686 rpm - [added] Building i686 rpm
- [changed] Restructured example config - [changed] Restructured example config
- [changed] Changed Rust version to 1.51.0 - [changed] Changed Rust version to 1.50.0
- [changed] Updated dependencies - [changed] Updated dependencies
- [changed] Change permissions of /etc/vpncloud - [changed] Change permissions of /etc/vpncloud

160
Cargo.lock generated
View File

@ -102,9 +102,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]] [[package]]
name = "bytes" name = "bytes"
@ -171,9 +171,9 @@ dependencies = [
[[package]] [[package]]
name = "const_fn" name = "const_fn"
version = "0.4.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 = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
[[package]] [[package]]
name = "cpuid-bool" name = "cpuid-bool"
@ -240,33 +240,35 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.3" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" checksum = "d60ab4a8dba064f2fbb5aa270c28da5cf4bbd0e72dae1140a6b0353a779dbe00"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"crossbeam-utils", "crossbeam-utils",
"lazy_static", "lazy_static",
"loom",
"memoffset", "memoffset",
"scopeguard", "scopeguard",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.3" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"lazy_static", "lazy_static",
"loom",
] ]
[[package]] [[package]]
name = "csv" name = "csv"
version = "1.1.6" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97"
dependencies = [ dependencies = [
"bstr", "bstr",
"csv-core", "csv-core",
@ -323,9 +325,9 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]] [[package]]
name = "dtoa" name = "dtoa"
version = "0.4.8" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
[[package]] [[package]]
name = "either" name = "either"
@ -355,6 +357,19 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "generator"
version = "0.6.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c"
dependencies = [
"cc",
"libc",
"log",
"rustversion",
"winapi",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.4" version = "0.14.4"
@ -491,9 +506,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.50" version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -506,9 +521,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.93" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
@ -534,6 +549,17 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "loom"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4"
dependencies = [
"cfg-if 1.0.0",
"generator",
"scoped-tls",
]
[[package]] [[package]]
name = "matches" name = "matches"
version = "0.1.8" version = "0.1.8"
@ -548,9 +574,9 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.6.3" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -633,9 +659,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.7.2" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
@ -762,9 +788,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.26" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -854,9 +880,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.4.5" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [ dependencies = [
"regex-syntax", "regex-syntax",
] ]
@ -872,9 +898,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.23" version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[package]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
@ -909,6 +935,12 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustversion"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -924,6 +956,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -947,9 +985,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.125" version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -966,9 +1004,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.125" version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -977,9 +1015,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.64" version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1061,9 +1099,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "standback" name = "standback"
version = "0.2.17" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8"
dependencies = [ dependencies = [
"version_check", "version_check",
] ]
@ -1149,9 +1187,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.68" version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1251,9 +1289,9 @@ dependencies = [
[[package]] [[package]]
name = "tinytemplate" name = "tinytemplate"
version = "1.2.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@ -1261,9 +1299,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.2.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@ -1327,9 +1365,9 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.13.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
@ -1399,9 +1437,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.3" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]] [[package]]
name = "void" name = "void"
@ -1411,7 +1449,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]] [[package]]
name = "vpncloud" name = "vpncloud"
version = "2.2.0" version = "2.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"byteorder", "byteorder",
@ -1443,9 +1481,9 @@ dependencies = [
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.2" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
dependencies = [ dependencies = [
"same-file", "same-file",
"winapi", "winapi",
@ -1460,9 +1498,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.73" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -1470,9 +1508,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.73" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
@ -1485,9 +1523,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.73" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1495,9 +1533,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.73" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1508,15 +1546,15 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.73" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.50" version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -1524,9 +1562,9 @@ dependencies = [
[[package]] [[package]]
name = "wildmatch" name = "wildmatch"
version = "1.1.0" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a" checksum = "d2399814fda0d6999a6bfe9b5c7660d836334deb162a09db8b73d5b38fd8c40a"
[[package]] [[package]]
name = "winapi" name = "winapi"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "vpncloud" name = "vpncloud"
version = "2.2.0" version = "2.1.0"
authors = ["Dennis Schwerdel <schwerdel@googlemail.com>"] authors = ["Dennis Schwerdel <schwerdel@googlemail.com>"]
build = "build.rs" build = "build.rs"
license = "GPL-3.0" license = "GPL-3.0"
@ -12,7 +12,7 @@ readme = "README.md"
edition = "2018" edition = "2018"
[package.metadata] [package.metadata]
toolchain = "1.51.0" toolchain = "1.50.0"
upx_version = "3.96" upx_version = "3.96"
[dependencies] [dependencies]
@ -86,7 +86,6 @@ assets = [
["target/release/vpncloud", "/usr/bin/vpncloud", "755"], ["target/release/vpncloud", "/usr/bin/vpncloud", "755"],
["assets/example.net.disabled", "/etc/vpncloud/example.net.disabled", "600"], ["assets/example.net.disabled", "/etc/vpncloud/example.net.disabled", "600"],
["assets/vpncloud@.service", "/lib/systemd/system/vpncloud@.service", "644"], ["assets/vpncloud@.service", "/lib/systemd/system/vpncloud@.service", "644"],
["assets/vpncloud.target", "/lib/systemd/system/vpncloud.target", "644"],
["assets/vpncloud-wsproxy.service", "/lib/systemd/system/vpncloud-wsproxy.service", "644"], ["assets/vpncloud-wsproxy.service", "/lib/systemd/system/vpncloud-wsproxy.service", "644"],
["target/vpncloud.1.gz", "/usr/share/man/man1/vpncloud.1.gz", "644"] ["target/vpncloud.1.gz", "/usr/share/man/man1/vpncloud.1.gz", "644"]
] ]

View File

@ -19,7 +19,7 @@ peers:
- REMOTE_HOST:PORT - REMOTE_HOST:PORT
``` ```
For more information, please see the [Website](https://vpncloud.ddswd.de) or the [Discussions group](https://github.com/dswd/vpncloud/discussions). For more information, please see the [Website](https://vpncloud.ddswd.de) or the [Forum](https://groups.google.com/forum/#!forum/vpncloud).
### Project Status ### Project Status

View File

@ -1,17 +1,3 @@
vpncloud (2.2.0) stable; urgency=medium
* [added] Service target file (thanks to mnhauke)
* [added] Added interactive configuration wizard
* [added] Support for (un-)installation
* [added] Building static binaries
* [added] Building i686 rpm
* [changed] Restructured example config
* [changed] Changed Rust version to 1.51.0
* [changed] Updated dependencies
* [changed] Change permissions of /etc/vpncloud
-- Dennis Schwerdel <schwerdel+vpncloud@googlemail.com> Tue, 06 Apr 2021 12:27:00 +0200
vpncloud (2.1.0) stable; urgency=medium vpncloud (2.1.0) stable; urgency=medium
* [added] Support for websocket proxy mode * [added] Support for websocket proxy mode
@ -23,7 +9,7 @@ vpncloud (2.1.0) stable; urgency=medium
* [fixed] Added missing peer address propagation * [fixed] Added missing peer address propagation
* [fixed] Fixed problem with peer addresses without port * [fixed] Fixed problem with peer addresses without port
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 06 Feb 2021 13:13:00 +0100 -- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 06 Feb 2020 13:13:00 +0100
vpncloud (2.0.1) stable; urgency=medium vpncloud (2.0.1) stable; urgency=medium

View File

@ -1,2 +0,0 @@
[Unit]
Description=VpnCloud target allowing to start/stop all vpncloud@.service instances at once

View File

@ -2,7 +2,6 @@
Description=VpnCloud network '%I' Description=VpnCloud network '%I'
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
PartOf=vpncloud.target
Documentation=man:vpncloud(1) Documentation=man:vpncloud(1)
[Service] [Service]

View File

@ -1,13 +1,9 @@
FROM ubuntu FROM ubuntu
RUN apt-get update && apt-get install -y asciinema locales bash iputils-ping RUN apt-get update && apt-get install -y asciinema
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN mkdir /root/.asciinema RUN mkdir /root/.asciinema
RUN mkdir /etc/vpncloud RUN mkdir /etc/vpncloud
WORKDIR /data WORKDIR /data
ADD config /root/.asciinema/config ADD config /root/.asciinema/config
RUN echo 'PS1="\[\e[00;34m\]\[\e[01;31m\]\u\[\e[00;01;34m\]@\[\e[00;34m\]node\[\e[01;31m\]:\[\e[00;34m\]\w\[\e[01;31m\]> \[\e[00m\]"' >> /root/.bashrc RUN echo 'PS1="\[\e[00;34m\]\[\e[01;31m\]\u\[\e[00;01;34m\]@\[\e[00;34m\]node\[\e[01;31m\]:\[\e[00;34m\]\w\[\e[01;31m\]> \[\e[00m\]"' >> /root/.bashrc

View File

@ -1,3 +1,3 @@
[record] [record]
command = bash -l command = /usr/bin/bash -l
idle_time_limit = 2.5 idle_time_limit = 2.5

View File

@ -0,0 +1,6 @@
#!/bin/bash
set -e
docker build -t asciinema-recorder .
docker run -it --rm --network host -v $(pwd)/../../target/release/:/usr/local/bin/ -v $(pwd):/data asciinema-recorder asciinema "$@"

View File

@ -1,11 +0,0 @@
#!/bin/bash
set -e
cd $(dirname $0)
docker build -t asciinema-recorder .
docker run -it --rm --network host \
-v $(pwd):/data \
-v /etc/hosts:/etc/hosts \
asciinema-recorder

View File

@ -1,165 +0,0 @@
{
"meta": {
"region": "eu-central-1",
"instance_type": "m5.large",
"ami": "ami-0db9040eb3ab74509",
"version": "2.2.0",
"duration": 623.0307722091675
},
"native": {
"iperf": {
"throughput": 9680235000.0,
"cpu_sender": 12.015535,
"cpu_receiver": 71.982452
},
"ping_100": {
"rtt_min": 0.049,
"rtt_max": 0.219,
"rtt_avg": 0.058,
"pkt_loss": 0.0
},
"ping_500": {
"rtt_min": 0.053,
"rtt_max": 0.247,
"rtt_avg": 0.059,
"pkt_loss": 0.0
},
"ping_1000": {
"rtt_min": 0.053,
"rtt_max": 0.189,
"rtt_avg": 0.06,
"pkt_loss": 0.0
}
},
"plain": {
"iperf": {
"throughput": 5790600000.0,
"cpu_sender": 14.109763,
"cpu_receiver": 69.727033
},
"ping_100": {
"rtt_min": 0.079,
"rtt_max": 0.291,
"rtt_avg": 0.094,
"pkt_loss": 0.0
},
"ping_500": {
"rtt_min": 0.079,
"rtt_max": 0.304,
"rtt_avg": 0.096,
"pkt_loss": 0.0
},
"ping_1000": {
"rtt_min": 0.082,
"rtt_max": 0.367,
"rtt_avg": 0.097,
"pkt_loss": 0.0
}
},
"aes256": {
"iperf": {
"throughput": 3917767000.0,
"cpu_sender": 6.439156,
"cpu_receiver": 64.267206
},
"ping_100": {
"rtt_min": 0.081,
"rtt_max": 0.206,
"rtt_avg": 0.097,
"pkt_loss": 0.0
},
"ping_500": {
"rtt_min": 0.088,
"rtt_max": 0.206,
"rtt_avg": 0.1,
"pkt_loss": 0.0
},
"ping_1000": {
"rtt_min": 0.089,
"rtt_max": 0.319,
"rtt_avg": 0.103,
"pkt_loss": 0.0
}
},
"aes128": {
"iperf": {
"throughput": 3697142000.0,
"cpu_sender": 7.417808,
"cpu_receiver": 59.433831
},
"ping_100": {
"rtt_min": 0.083,
"rtt_max": 0.265,
"rtt_avg": 0.097,
"pkt_loss": 0.0
},
"ping_500": {
"rtt_min": 0.081,
"rtt_max": 0.369,
"rtt_avg": 0.102,
"pkt_loss": 0.0
},
"ping_1000": {
"rtt_min": 0.086,
"rtt_max": 0.448,
"rtt_avg": 0.102,
"pkt_loss": 0.0
}
},
"chacha20": {
"iperf": {
"throughput": 3194412000.0,
"cpu_sender": 6.12856,
"cpu_receiver": 61.223349
},
"ping_100": {
"rtt_min": 0.081,
"rtt_max": 0.28,
"rtt_avg": 0.098,
"pkt_loss": 0.0
},
"ping_500": {
"rtt_min": 0.088,
"rtt_max": 0.264,
"rtt_avg": 0.103,
"pkt_loss": 0.0
},
"ping_1000": {
"rtt_min": 0.092,
"rtt_max": 0.204,
"rtt_avg": 0.106,
"pkt_loss": 0.0
}
},
"results": {
"throughput_mbits": {
"native": 9680.235,
"plain": 5790.6,
"aes256": 3917.767,
"aes128": 3697.142,
"chacha20": 3194.412
},
"latency_us": {
"plain": {
"100": 18.0,
"500": 18.500000000000004,
"1000": 18.500000000000004
},
"aes256": {
"100": 19.5,
"500": 20.500000000000004,
"1000": 21.5
},
"aes128": {
"100": 19.5,
"500": 21.5,
"1000": 20.999999999999996
},
"chacha20": {
"100": 20.0,
"500": 22.0,
"1000": 23.0
}
}
}
}

View File

@ -7,8 +7,8 @@ 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.2.0" VERSION = "2.1.0"
REGION = "eu-central-1" REGION = "eu-central-1"
env = EC2Environment( env = EC2Environment(
@ -113,4 +113,4 @@ name = "measurements/{date}_{version}_perf.json".format(date=date.today().strfti
eprint('Storing results in {}'.format(name)) eprint('Storing results in {}'.format(name))
with open(name, 'w') as fp: with open(name, 'w') as fp:
json.dump(results, fp, indent=2) json.dump(results, fp, indent=2)
eprint("done.") eprint("done.")

View File

@ -15,15 +15,16 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, Arc, Mutex
}, },
thread, thread
}; };
use super::util::{from_base62, to_base62, Encoder, TimeSource}; use super::util::{from_base62, to_base62, Encoder, TimeSource};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
const TYPE_BEGIN: u8 = 0; const TYPE_BEGIN: u8 = 0;
const TYPE_END: u8 = 1; const TYPE_END: u8 = 1;
const TYPE_DATA: u8 = 2; const TYPE_DATA: u8 = 2;
@ -39,14 +40,14 @@ fn sha512(data: &[u8]) -> SmallVec<[u8; 64]> {
struct FutureResult<T> { struct FutureResult<T> {
has_result: AtomicBool, has_result: AtomicBool,
result: Mutex<T>, result: Mutex<T>
} }
#[derive(Clone)] #[derive(Clone)]
pub struct BeaconSerializer<TS> { pub struct BeaconSerializer<TS> {
shared_key: Vec<u8>, shared_key: Vec<u8>,
future_peers: Arc<FutureResult<Vec<SocketAddr>>>, future_peers: Arc<FutureResult<Vec<SocketAddr>>>,
_dummy_ts: PhantomData<TS>, _dummy_ts: PhantomData<TS>
} }
impl<TS: TimeSource> BeaconSerializer<TS> { impl<TS: TimeSource> BeaconSerializer<TS> {
@ -54,7 +55,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
Self { Self {
shared_key: shared_key.to_owned(), shared_key: shared_key.to_owned(),
future_peers: Arc::new(FutureResult { has_result: AtomicBool::new(false), result: Mutex::new(Vec::new()) }), future_peers: Arc::new(FutureResult { has_result: AtomicBool::new(false), result: Mutex::new(Vec::new()) }),
_dummy_ts: PhantomData, _dummy_ts: PhantomData
} }
} }
@ -104,7 +105,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
fn decrypt_data(&self, data: &mut Vec<u8>) -> bool { fn decrypt_data(&self, data: &mut Vec<u8>) -> bool {
if data.is_empty() { if data.is_empty() {
return false; return false
} }
let seed = data.pop().unwrap() ^ self.get_keystream(TYPE_SEED, 0, 0)[0]; let seed = data.pop().unwrap() ^ self.get_keystream(TYPE_SEED, 0, 0)[0];
self.mask_with_keystream(data as &mut [u8], TYPE_DATA, seed); self.mask_with_keystream(data as &mut [u8], TYPE_DATA, seed);
@ -121,7 +122,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
for p in peers { for p in peers {
match *p { match *p {
SocketAddr::V4(addr) => v4addrs.push(addr), SocketAddr::V4(addr) => v4addrs.push(addr),
SocketAddr::V6(addr) => v6addrs.push(addr), SocketAddr::V6(addr) => v6addrs.push(addr)
} }
} }
// Add count of v4 addresses // Add count of v4 addresses
@ -157,23 +158,23 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
let mut peers = Vec::new(); let mut peers = Vec::new();
let mut pos = 0; let mut pos = 0;
if data.len() < 4 { if data.len() < 4 {
return peers; return peers
} }
if !self.decrypt_data(&mut data) { if !self.decrypt_data(&mut data) {
return peers; return peers
} }
let then = Wrapping(Encoder::read_u16(&data[pos..=pos + 1])); let then = Wrapping(Encoder::read_u16(&data[pos..=pos + 1]));
if let Some(ttl) = ttl_hours { if let Some(ttl) = ttl_hours {
let now = Wrapping(Self::now_hour_16()); let now = Wrapping(Self::now_hour_16());
if now - then > Wrapping(ttl) && then - now > Wrapping(ttl) { if now - then > Wrapping(ttl) && then - now > Wrapping(ttl) {
return peers; return peers
} }
} }
pos += 2; pos += 2;
let v4count = data[pos] as usize; let v4count = data[pos] as usize;
pos += 1; pos += 1;
if v4count * 6 > data.len() - pos || (data.len() - pos - v4count * 6) % 18 > 0 { if v4count * 6 > data.len() - pos || (data.len() - pos - v4count * 6) % 18 > 0 {
return peers; return peers
} }
for _ in 0..v4count { for _ in 0..v4count {
assert!(data.len() >= pos + 6); assert!(data.len() >= pos + 6);
@ -197,7 +198,7 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
Ipv6Addr::new(ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]), Ipv6Addr::new(ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]),
port, port,
0, 0,
0, 0
)); ));
peers.push(addr); peers.push(addr);
} }
@ -261,14 +262,14 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
peers.append(&mut self.peerlist_decode(&data[start_pos..end_pos], ttl_hours)); peers.append(&mut self.peerlist_decode(&data[start_pos..end_pos], ttl_hours));
pos = start_pos pos = start_pos
} else { } else {
break; break
} }
} }
peers peers
} }
pub fn read_from_file<P: AsRef<Path>>( pub fn read_from_file<P: AsRef<Path>>(
&self, path: P, ttl_hours: Option<u16>, &self, path: P, ttl_hours: Option<u16>
) -> Result<Vec<SocketAddr>, io::Error> { ) -> Result<Vec<SocketAddr>, io::Error> {
let mut f = File::open(&path)?; let mut f = File::open(&path)?;
let mut contents = String::new(); let mut contents = String::new();
@ -315,22 +316,26 @@ impl<TS: TimeSource> BeaconSerializer<TS> {
} }
} }
#[cfg(test)]
use crate::util::MockTimeSource; #[cfg(test)] use crate::util::MockTimeSource;
#[cfg(test)] #[cfg(test)] use std::str::FromStr;
use std::str::FromStr; #[cfg(test)] use std::time::Duration;
#[cfg(test)]
use std::time::Duration;
#[test] #[test]
async fn encode() { async fn encode() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let mut peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let mut peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
assert_eq!("WsHI31EWDMBYxvITiILIrm2k9gEik22E", ser.encode(&peers)); assert_eq!("WsHI31EWDMBYxvITiILIrm2k9gEik22E", ser.encode(&peers));
peers.push(SocketAddr::from_str("[::1]:5678").unwrap()); peers.push(SocketAddr::from_str("[::1]:5678").unwrap());
assert_eq!("WsHI3GXKaXCveo6uejmZizZ72kR6Y0L9T7h49TXONp1ugfKvvvEik22E", ser.encode(&peers)); assert_eq!("WsHI3GXKaXCveo6uejmZizZ72kR6Y0L9T7h49TXONp1ugfKvvvEik22E", ser.encode(&peers));
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:54").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:54").unwrap()
];
assert_eq!("WsHI32gm9eMSHP3Lm1GXcdP7rD3ik22E", ser.encode(&peers)); assert_eq!("WsHI32gm9eMSHP3Lm1GXcdP7rD3ik22E", ser.encode(&peers));
} }
@ -338,7 +343,10 @@ async fn encode() {
async fn decode() { async fn decode() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let mut peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let mut peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
assert_eq!(format!("{:?}", peers), format!("{:?}", ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None))); assert_eq!(format!("{:?}", peers), format!("{:?}", ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None)));
peers.push(SocketAddr::from_str("[::1]:5678").unwrap()); peers.push(SocketAddr::from_str("[::1]:5678").unwrap());
assert_eq!( assert_eq!(
@ -351,7 +359,10 @@ async fn decode() {
async fn decode_split() { async fn decode_split() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
assert_eq!( assert_eq!(
format!("{:?}", peers), format!("{:?}", peers),
format!("{:?}", ser.decode("WsHI3-1E.WD:MB Yx\tvI\nTi(IL)Ir[m2]k9ügEäik22E", None)) format!("{:?}", ser.decode("WsHI3-1E.WD:MB Yx\tvI\nTi(IL)Ir[m2]k9ügEäik22E", None))
@ -366,7 +377,10 @@ async fn decode_split() {
async fn decode_offset() { async fn decode_offset() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
assert_eq!( assert_eq!(
format!("{:?}", peers), format!("{:?}", peers),
format!("{:?}", ser.decode("Hello World: WsHI31EWDMBYxvITiILIrm2k9gEik22E! End of the World", None)) format!("{:?}", ser.decode("Hello World: WsHI31EWDMBYxvITiILIrm2k9gEik22E! End of the World", None))
@ -377,7 +391,10 @@ async fn decode_offset() {
async fn decode_multiple() { async fn decode_multiple() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
assert_eq!( assert_eq!(
format!("{:?}", peers), format!("{:?}", peers),
format!("{:?}", ser.decode("WsHI31HVpqxFNMNSPrvik22E WsHI34yOBcZIulKdtn2ik22E", None)) format!("{:?}", ser.decode("WsHI31HVpqxFNMNSPrvik22E WsHI34yOBcZIulKdtn2ik22E", None))
@ -421,11 +438,15 @@ async fn decode_invalid() {
assert_eq!(2, ser.decode("WsHI3WsHI31EWDMBYxvITiILIrm2k9gEik22Eik22E", None).len()); assert_eq!(2, ser.decode("WsHI3WsHI31EWDMBYxvITiILIrm2k9gEik22Eik22E", None).len());
} }
#[test] #[test]
async fn encode_decode() { async fn encode_decode() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let data = ser.encode(&peers); let data = ser.encode(&peers);
let peers2 = ser.decode(&data, None); let peers2 = ser.decode(&data, None);
assert_eq!(format!("{:?}", peers), format!("{:?}", peers2)); assert_eq!(format!("{:?}", peers), format!("{:?}", peers2));
@ -435,7 +456,10 @@ async fn encode_decode() {
async fn encode_decode_file() { async fn encode_decode_file() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
assert!(ser.write_to_file(&peers, file.path()).is_ok()); assert!(ser.write_to_file(&peers, file.path()).is_ok());
let peers2 = ser.read_from_file(file.path(), None); let peers2 = ser.read_from_file(file.path(), None);
@ -447,7 +471,10 @@ async fn encode_decode_file() {
async fn encode_decode_cmd() { async fn encode_decode_cmd() {
MockTimeSource::set_time(2000 * 3600); MockTimeSource::set_time(2000 * 3600);
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey"); let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
let peers = vec![SocketAddr::from_str("1.2.3.4:5678").unwrap(), SocketAddr::from_str("6.6.6.6:53").unwrap()]; let peers = vec![
SocketAddr::from_str("1.2.3.4:5678").unwrap(),
SocketAddr::from_str("6.6.6.6:53").unwrap()
];
let file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); let file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
assert!(ser.write_to_cmd(&peers, &format!("echo $beacon > {}", file.path().display())).is_ok()); assert!(ser.write_to_cmd(&peers, &format!("echo $beacon > {}", file.path().display())).is_ok());
thread::sleep(Duration::from_millis(100)); thread::sleep(Duration::from_millis(100));

View File

@ -2,15 +2,22 @@
// Copyright (C) 2015-2021 Dennis Schwerdel // Copyright (C) 2015-2021 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::run_cmd, 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 std::{cmp::max, collections::HashMap, ffi::OsStr, process, thread}; use std::{
cmp::max,
collections::HashMap,
ffi::OsStr,
process,
thread
};
use structopt::{clap::Shell, StructOpt}; 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;
#[derive(Deserialize, Debug, PartialEq, Clone)] #[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct Config { pub struct Config {
pub device_type: Type, pub device_type: Type,
@ -20,7 +27,6 @@ pub struct Config {
pub fix_rp_filter: bool, pub fix_rp_filter: bool,
pub ip: Option<String>, pub ip: Option<String>,
pub advertise_addresses: Vec<String>,
pub ifup: Option<String>, pub ifup: Option<String>,
pub ifdown: Option<String>, pub ifdown: Option<String>,
@ -47,7 +53,7 @@ pub struct Config {
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String>, pub group: Option<String>,
pub hook: Option<String>, pub hook: Option<String>,
pub hooks: HashMap<String, String>, pub hooks: HashMap<String, String>
} }
impl Default for Config { impl Default for Config {
@ -59,7 +65,6 @@ impl Default for Config {
device_mtu: None, device_mtu: None,
fix_rp_filter: false, fix_rp_filter: false,
ip: None, ip: None,
advertise_addresses: vec![],
ifup: None, ifup: None,
ifdown: None, ifdown: None,
crypto: CryptoConfig::default(), crypto: CryptoConfig::default(),
@ -84,7 +89,7 @@ impl Default for Config {
user: None, user: None,
group: None, group: None,
hook: None, hook: None,
hooks: HashMap::new(), hooks: HashMap::new()
} }
} }
} }
@ -112,9 +117,6 @@ impl Config {
if let Some(val) = file.ip { if let Some(val) = file.ip {
self.ip = Some(val); self.ip = Some(val);
} }
if let Some(mut val) = file.advertise_addresses {
self.advertise_addresses.append(&mut val);
}
if let Some(val) = file.ifup { if let Some(val) = file.ifup {
self.ifup = Some(val); self.ifup = Some(val);
} }
@ -225,7 +227,6 @@ impl Config {
if let Some(val) = args.ifup { if let Some(val) = args.ifup {
self.ifup = Some(val); self.ifup = Some(val);
} }
self.advertise_addresses.append(&mut args.advertise_addresses);
if let Some(val) = args.ifdown { if let Some(val) = args.ifdown {
self.ifdown = Some(val); self.ifdown = Some(val);
} }
@ -302,7 +303,7 @@ impl Config {
if s.contains(':') { if s.contains(':') {
let pos = s.find(':').unwrap(); let pos = s.find(':').unwrap();
let name = &s[..pos]; let name = &s[..pos];
let hook = &s[pos + 1..]; let hook = &s[pos+1..];
self.hooks.insert(name.to_string(), hook.to_string()); self.hooks.insert(name.to_string(), hook.to_string());
} else { } else {
self.hook = Some(s); self.hook = Some(s);
@ -318,14 +319,14 @@ impl Config {
store: self.beacon_store, store: self.beacon_store,
load: self.beacon_load, load: self.beacon_load,
interval: Some(self.beacon_interval), interval: Some(self.beacon_interval),
password: self.beacon_password, password: self.beacon_password
}), }),
device: Some(ConfigFileDevice { device: Some(ConfigFileDevice {
name: Some(self.device_name), name: Some(self.device_name),
path: self.device_path, path: self.device_path,
mtu: self.device_mtu, mtu: self.device_mtu,
type_: Some(self.device_type), type_: Some(self.device_type),
fix_rp_filter: Some(self.fix_rp_filter), fix_rp_filter: Some(self.fix_rp_filter)
}), }),
crypto: self.crypto, crypto: self.crypto,
group: self.group, group: self.group,
@ -333,7 +334,6 @@ impl Config {
ifup: self.ifup, ifup: self.ifup,
ifdown: self.ifdown, ifdown: self.ifdown,
ip: self.ip, ip: self.ip,
advertise_addresses: Some(self.advertise_addresses),
keepalive: self.keepalive, keepalive: self.keepalive,
listen: Some(self.listen), listen: Some(self.listen),
mode: Some(self.mode), mode: Some(self.mode),
@ -342,10 +342,13 @@ impl Config {
pid_file: self.pid_file, pid_file: self.pid_file,
port_forwarding: Some(self.port_forwarding), port_forwarding: Some(self.port_forwarding),
stats_file: self.stats_file, stats_file: self.stats_file,
statsd: Some(ConfigFileStatsd { server: self.statsd_server, prefix: self.statsd_prefix }), statsd: Some(ConfigFileStatsd {
server: self.statsd_server,
prefix: self.statsd_prefix
}),
switch_timeout: Some(self.switch_timeout), switch_timeout: Some(self.switch_timeout),
hook: self.hook, hook: self.hook,
hooks: self.hooks, hooks: self.hooks
} }
} }
@ -385,7 +388,7 @@ impl Config {
} }
pub fn call_hook( pub fn call_hook(
&self, event: &'static str, envs: impl IntoIterator<Item = (&'static str, impl AsRef<OsStr>)>, detach: bool, &self, event: &'static str, envs: impl IntoIterator<Item = (&'static str, impl AsRef<OsStr>)>, detach: bool
) { ) {
let mut script = None; let mut script = None;
if let Some(ref s) = self.hook { if let Some(ref s) = self.hook {
@ -395,7 +398,7 @@ impl Config {
script = Some(s); script = Some(s);
} }
if script.is_none() { if script.is_none() {
return; return
} }
let script = script.unwrap(); let script = script.unwrap();
let mut cmd = process::Command::new("sh"); let mut cmd = process::Command::new("sh");
@ -515,10 +518,6 @@ pub struct Args {
#[structopt(long)] #[structopt(long)]
pub ip: Option<String>, pub ip: Option<String>,
/// A list of IP Addresses to advertise as our external address(s)
#[structopt(long = "advertise_addresses", use_delimiter = true)]
pub advertise_addresses: Vec<String>,
/// A command to setup the network interface /// A command to setup the network interface
#[structopt(long)] #[structopt(long)]
pub ifup: Option<String>, pub ifup: Option<String>,
@ -590,8 +589,8 @@ pub enum Command {
#[structopt(alias = "wsproxy")] #[structopt(alias = "wsproxy")]
WsProxy { WsProxy {
/// Websocket listen address IP:PORT /// Websocket listen address IP:PORT
#[structopt(long, short, default_value = "3210")] #[structopt(long, short, default_value="3210")]
listen: String, listen: String
}, },
/// Migrate an old config file /// Migrate an old config file
@ -605,8 +604,8 @@ pub enum Command {
/// Generate shell completions /// Generate shell completions
Completion { Completion {
/// Shell to create completions for /// Shell to create completions for
#[structopt(long, default_value = "bash")] #[structopt(long, default_value="bash")]
shell: Shell, shell: Shell
}, },
/// Edit the config of a network /// Edit the config of a network
@ -614,7 +613,7 @@ pub enum Command {
Config { Config {
/// Name of the network /// Name of the network
#[structopt(short, long)] #[structopt(short, long)]
name: Option<String>, name: Option<String>
}, },
/// Install required utility files /// Install required utility files
@ -622,8 +621,8 @@ pub enum Command {
Install { Install {
/// Remove installed files again /// Remove installed files again
#[structopt(long)] #[structopt(long)]
uninstall: bool, uninstall: bool
}, }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -659,7 +658,6 @@ pub struct ConfigFile {
pub device: Option<ConfigFileDevice>, pub device: Option<ConfigFileDevice>,
pub ip: Option<String>, pub ip: Option<String>,
pub advertise_addresses: Option<Vec<String>>,
pub ifup: Option<String>, pub ifup: Option<String>,
pub ifdown: Option<String>, pub ifdown: Option<String>,
@ -681,7 +679,7 @@ pub struct ConfigFile {
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String>, pub group: Option<String>,
pub hook: Option<String>, pub hook: Option<String>,
pub hooks: HashMap<String, String>, pub hooks: HashMap<String, String>
} }
#[test] #[test]
@ -693,9 +691,6 @@ device:
path: /dev/net/tun path: /dev/net/tun
mtu: 1400 mtu: 1400
ip: 10.0.1.1/16 ip: 10.0.1.1/16
advertise-addresses:
- 192.168.0.1
- 192.168.1.1
ifup: ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up ifup: ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up
ifdown: 'true' ifdown: 'true'
peers: peers:
@ -732,7 +727,6 @@ statsd:
fix_rp_filter: None fix_rp_filter: None
}), }),
ip: Some("10.0.1.1/16".to_string()), ip: Some("10.0.1.1/16".to_string()),
advertise_addresses: Some(vec!["192.168.0.1".to_string(), "192.168.1.1".to_string()]),
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()),
crypto: CryptoConfig::default(), crypto: CryptoConfig::default(),
@ -782,7 +776,6 @@ async fn config_merge() {
fix_rp_filter: None, fix_rp_filter: None,
}), }),
ip: None, ip: None,
advertise_addresses: Some(vec![]),
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()),
crypto: CryptoConfig::default(), crypto: CryptoConfig::default(),
@ -810,39 +803,35 @@ async fn config_merge() {
prefix: Some("prefix".to_string()), prefix: Some("prefix".to_string()),
}), }),
hook: None, hook: None,
hooks: HashMap::new(), hooks: HashMap::new()
});
assert_eq!(config, Config {
device_type: Type::Tun,
device_name: "vpncloud%d".to_string(),
device_path: None,
ip: None,
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
listen: "3210".to_string(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600,
keepalive: Some(840),
switch_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
beacon_password: Some("test123".to_string()),
mode: Mode::Normal,
port_forwarding: true,
claims: vec!["10.0.1.0/24".to_string()],
user: Some("nobody".to_string()),
group: Some("nogroup".to_string()),
pid_file: Some("/run/vpncloud.run".to_string()),
stats_file: Some("/var/log/vpncloud.stats".to_string()),
statsd_server: Some("example.com:1234".to_string()),
statsd_prefix: Some("prefix".to_string()),
..Default::default()
}); });
assert_eq!(
config,
Config {
device_type: Type::Tun,
device_name: "vpncloud%d".to_string(),
device_path: None,
ip: None,
advertise_addresses: vec![],
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
ifdown: Some("true".to_string()),
listen: "3210".to_string(),
peers: vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()],
peer_timeout: 600,
keepalive: Some(840),
switch_timeout: 300,
beacon_store: Some("/run/vpncloud.beacon.out".to_string()),
beacon_load: Some("/run/vpncloud.beacon.in".to_string()),
beacon_interval: 7200,
beacon_password: Some("test123".to_string()),
mode: Mode::Normal,
port_forwarding: true,
claims: vec!["10.0.1.0/24".to_string()],
user: Some("nobody".to_string()),
group: Some("nogroup".to_string()),
pid_file: Some("/run/vpncloud.run".to_string()),
stats_file: Some("/var/log/vpncloud.stats".to_string()),
statsd_server: Some("example.com:1234".to_string()),
statsd_prefix: Some("prefix".to_string()),
..Default::default()
}
);
config.merge_args(Args { config.merge_args(Args {
type_: Some(Type::Tap), type_: Some(Type::Tap),
device: Some("vpncloud0".to_string()), device: Some("vpncloud0".to_string()),
@ -877,7 +866,6 @@ async fn config_merge() {
device_path: Some("/dev/null".to_string()), device_path: Some("/dev/null".to_string()),
device_mtu: None, device_mtu: None,
fix_rp_filter: false, fix_rp_filter: false,
advertise_addresses: vec![],
ip: None, ip: None,
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()),

View File

@ -8,6 +8,7 @@ use crate::{
types::NodeId, types::NodeId,
util::{from_base62, to_base62, MsgBuffer}, util::{from_base62, to_base62, MsgBuffer},
}; };
use libc::BPF_FS_MAGIC;
use ring::{ use ring::{
aead::{self, Algorithm, LessSafeKey, UnboundKey}, aead::{self, Algorithm, LessSafeKey, UnboundKey},
agreement::{EphemeralPrivateKey, UnparsedPublicKey}, agreement::{EphemeralPrivateKey, UnparsedPublicKey},
@ -252,8 +253,8 @@ impl PeerCrypto {
if msg.stage() == STAGE_PONG { if msg.stage() == STAGE_PONG {
buffer.set_length(last_init_message.len()); buffer.set_length(last_init_message.len());
buffer.message_mut().copy_from_slice(last_init_message); buffer.message_mut().copy_from_slice(last_init_message);
return Ok(MessageResult::Reply)
} }
return Ok(MessageResult::Reply)
} }
} }
Ok(MessageResult::None) Ok(MessageResult::None)

View File

@ -44,7 +44,7 @@
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use ring::{ use ring::{
aead::{self, LessSafeKey, UnboundKey}, aead::{self, LessSafeKey, UnboundKey},
rand::{SecureRandom, SystemRandom}, rand::{SecureRandom, SystemRandom}
}; };
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
@ -57,10 +57,12 @@ use std::{
use crate::{error::Error, util::MsgBuffer}; use crate::{error::Error, util::MsgBuffer};
const NONCE_LEN: usize = 12; const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16; pub const TAG_LEN: usize = 16;
pub const EXTRA_LEN: usize = 8; pub const EXTRA_LEN: usize = 8;
fn random_data(size: usize) -> Vec<u8> { fn random_data(size: usize) -> Vec<u8> {
let rand = SystemRandom::new(); let rand = SystemRandom::new();
let mut data = vec![0; size]; let mut data = vec![0; size];
@ -96,7 +98,7 @@ impl Nonce {
num = num.wrapping_add(1); num = num.wrapping_add(1);
self.0[i] = num; self.0[i] = num;
if num > 0 { if num > 0 {
return; return
} }
} }
} }
@ -107,7 +109,7 @@ struct CryptoKey {
send_nonce: Nonce, send_nonce: Nonce,
min_nonce: Nonce, min_nonce: Nonce,
next_min_nonce: Nonce, next_min_nonce: Nonce,
seen_nonce: Nonce, seen_nonce: Nonce
} }
impl CryptoKey { impl CryptoKey {
@ -119,7 +121,7 @@ impl CryptoKey {
send_nonce, send_nonce,
min_nonce: Nonce::zero(), min_nonce: Nonce::zero(),
next_min_nonce: Nonce::zero(), next_min_nonce: Nonce::zero(),
seen_nonce: Nonce::zero(), seen_nonce: Nonce::zero()
} }
} }
@ -188,7 +190,7 @@ impl CryptoCore {
fn decrypt_with_key(key: &mut CryptoKey, nonce: Nonce, data_and_tag: &mut [u8]) -> Result<(), Error> { fn decrypt_with_key(key: &mut CryptoKey, nonce: Nonce, data_and_tag: &mut [u8]) -> Result<(), Error> {
if nonce < key.min_nonce { if nonce < key.min_nonce {
return Err(Error::Crypto("Old nonce rejected")); return Err(Error::Crypto("Old nonce rejected"))
} }
// decrypt // decrypt
let crypto_nonce = aead::Nonce::assume_unique_for_key(*nonce.as_bytes()); let crypto_nonce = aead::Nonce::assume_unique_for_key(*nonce.as_bytes());
@ -271,6 +273,7 @@ pub fn test_speed(algo: &'static aead::Algorithm, max_time: &Duration) -> f64 {
data as f64 / duration / 1_000_000.0 data as f64 / duration / 1_000_000.0
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -313,6 +316,7 @@ mod tests {
test_encrypt_decrypt(&aead::CHACHA20_POLY1305) test_encrypt_decrypt(&aead::CHACHA20_POLY1305)
} }
fn test_tampering(algo: &'static aead::Algorithm) { fn test_tampering(algo: &'static aead::Algorithm) {
let (sender, receiver) = create_dummy_pair(algo); let (sender, receiver) = create_dummy_pair(algo);
let plain = random_data(1000); let plain = random_data(1000);
@ -443,6 +447,7 @@ mod tests {
test_key_rotation(&aead::CHACHA20_POLY1305); test_key_rotation(&aead::CHACHA20_POLY1305);
} }
#[test] #[test]
async fn test_core_size() { async fn test_core_size() {
assert_eq!(2400, mem::size_of::<CryptoCore>()); assert_eq!(2400, mem::size_of::<CryptoCore>());

View File

@ -7,5 +7,5 @@ mod core;
mod init; mod init;
mod rotate; mod rotate;
pub use self::core::{EXTRA_LEN, TAG_LEN};
pub use common::*; pub use common::*;
pub use self::core::{EXTRA_LEN, TAG_LEN};

View File

@ -34,18 +34,20 @@ use crate::{error::Error, util::MsgBuffer};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use ring::{ use ring::{
agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519}, agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519},
rand::SystemRandom, rand::SystemRandom
}; };
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::io::{self, Cursor, Read, Write}; use std::io::{self, Cursor, Read, Write};
type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>; type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>;
type EcdhPrivateKey = EphemeralPrivateKey; type EcdhPrivateKey = EphemeralPrivateKey;
pub struct RotationMessage { pub struct RotationMessage {
message_id: u64, message_id: u64,
propose: EcdhPublicKey, propose: EcdhPublicKey,
confirm: Option<EcdhPublicKey>, confirm: Option<EcdhPublicKey>
} }
impl RotationMessage { impl RotationMessage {
@ -89,13 +91,13 @@ pub struct RotationState {
pending: Option<(Key, EcdhPublicKey)>, // sent by remote, to be confirmed pending: Option<(Key, EcdhPublicKey)>, // sent by remote, to be confirmed
proposed: Option<EcdhPrivateKey>, // my own, proposed but not confirmed proposed: Option<EcdhPrivateKey>, // my own, proposed but not confirmed
message_id: u64, message_id: u64,
timeout: bool, timeout: bool
} }
pub struct RotatedKey { pub struct RotatedKey {
pub key: Key, pub key: Key,
pub id: u64, pub id: u64,
pub use_for_sending: bool, pub use_for_sending: bool
} }
impl RotationState { impl RotationState {
@ -153,7 +155,7 @@ impl RotationState {
pub fn process_message(&mut self, msg: RotationMessage) -> Option<RotatedKey> { pub fn process_message(&mut self, msg: RotationMessage) -> Option<RotatedKey> {
if msg.message_id <= self.message_id { if msg.message_id <= self.message_id {
return None; return None
} }
debug!("Received rotation message with id {}", msg.message_id); debug!("Received rotation message with id {}", msg.message_id);
self.timeout = false; self.timeout = false;
@ -165,7 +167,7 @@ impl RotationState {
if let Some(peer_key) = msg.confirm { if let Some(peer_key) = msg.confirm {
if let Some(private_key) = self.proposed.take() { if let Some(private_key) = self.proposed.take() {
let key = Self::derive_key(private_key, peer_key); let key = Self::derive_key(private_key, peer_key);
return Some(RotatedKey { key, id: msg.message_id, use_for_sending: true }); return Some(RotatedKey { key, id: msg.message_id, use_for_sending: true })
} }
} }
None None
@ -181,7 +183,7 @@ impl RotationState {
// Reconfirm last confirmed key // Reconfirm last confirmed key
Self::send( Self::send(
&RotationMessage { confirm: Some(confirmed_key.clone()), propose: proposed_key, message_id }, &RotationMessage { confirm: Some(confirmed_key.clone()), propose: proposed_key, message_id },
out, out
); );
} else { } else {
// First message has been lost // First message has been lost
@ -200,7 +202,7 @@ impl RotationState {
self.proposed = Some(private_key); self.proposed = Some(private_key);
self.confirmed = Some((confirm_key.clone(), message_id)); self.confirmed = Some((confirm_key.clone(), message_id));
Self::send(&RotationMessage { confirm: Some(confirm_key), propose: propose_key, message_id }, out); Self::send(&RotationMessage { confirm: Some(confirm_key), propose: propose_key, message_id }, out);
return Some(RotatedKey { key, id: message_id, use_for_sending: false }); return Some(RotatedKey { key, id: message_id, use_for_sending: false })
} else { } else {
// Nothing pending nor proposed, still waiting to receive message 1 // Nothing pending nor proposed, still waiting to receive message 1
// Do nothing, peer will retry // Do nothing, peer will retry
@ -219,7 +221,7 @@ mod tests {
impl MsgBuffer { impl MsgBuffer {
fn msg(&mut self) -> Option<RotationMessage> { fn msg(&mut self) -> Option<RotationMessage> {
if self.is_empty() { if self.is_empty() {
return None; return None
} }
let msg = RotationMessage::read_from(Cursor::new(self.message())).unwrap(); let msg = RotationMessage::read_from(Cursor::new(self.message())).unwrap();
self.set_length(0); self.set_length(0);

View File

@ -1,20 +1,20 @@
use super::{ use super::{
common::SPACE_BEFORE,
shared::{SharedPeerCrypto, SharedTable, SharedTraffic}, shared::{SharedPeerCrypto, SharedTable, SharedTraffic},
common::SPACE_BEFORE,
}; };
use crate::{ use crate::{
beacon::BeaconSerializer, beacon::BeaconSerializer,
config::{DEFAULT_PEER_TIMEOUT, DEFAULT_PORT}, config::{DEFAULT_PEER_TIMEOUT, DEFAULT_PORT},
crypto::{is_init_message, Crypto, InitResult, InitState, MessageResult}, crypto::{is_init_message, InitResult, InitState, MessageResult, Crypto},
device::{Device, Type}, device::{Type, Device},
engine::common::{Hash, PeerData}, engine::common::{Hash, PeerData},
error::Error, error::Error,
messages::{ messages::{
AddrList, NodeInfo, PeerInfo, MESSAGE_TYPE_CLOSE, MESSAGE_TYPE_DATA, MESSAGE_TYPE_KEEPALIVE, AddrList, NodeInfo, PeerInfo, MESSAGE_TYPE_CLOSE, MESSAGE_TYPE_DATA, MESSAGE_TYPE_KEEPALIVE,
MESSAGE_TYPE_NODE_INFO, MESSAGE_TYPE_NODE_INFO,
}, },
net::{mapped_addr, parse_listen, Socket}, net::{mapped_addr, Socket},
port_forwarding::PortForwarding, port_forwarding::PortForwarding,
types::{Address, NodeId, Range, RangeList}, types::{Address, NodeId, Range, RangeList},
util::{addr_nice, resolve, MsgBuffer, StatsdMsg, Time, TimeSource}, util::{addr_nice, resolve, MsgBuffer, StatsdMsg, Time, TimeSource},
@ -236,17 +236,7 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
async fn update_peer_info(&mut self, addr: SocketAddr, info: Option<NodeInfo>) -> Result<(), Error> { async fn update_peer_info(&mut self, addr: SocketAddr, info: Option<NodeInfo>) -> Result<(), Error> {
if let Some(peer) = self.peers.get_mut(&addr) { if let Some(peer) = self.peers.get_mut(&addr) {
peer.last_seen = TS::now(); peer.last_seen = TS::now();
peer.timeout = TS::now() + self.config.peer_timeout as Time; peer.timeout = TS::now() + self.config.peer_timeout as Time
if let Some(info) = &info {
// Update peer addresses, always add seen address
peer.addrs.clear();
peer.addrs.push(addr);
for addr in &info.addrs {
if !peer.addrs.contains(addr) {
peer.addrs.push(*addr);
}
}
}
} else { } else {
error!("Received peer update from non peer {}", addr_nice(addr)); error!("Received peer update from non peer {}", addr_nice(addr));
return Ok(()); return Ok(());
@ -291,12 +281,6 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
'outer: for peer in peers { 'outer: for peer in peers {
for addr in &peer.addrs { for addr in &peer.addrs {
if self.peers.contains_key(addr) { if self.peers.contains_key(addr) {
// Check addresses and add addresses that we don't know to own addresses
for addr in &peer.addrs {
if !self.own_addresses.contains(addr) {
self.own_addresses.push(*addr)
}
}
continue 'outer; continue 'outer;
} }
} }
@ -443,6 +427,11 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
pfw.check_extend(); pfw.check_extend();
} }
let now = TS::now(); let now = TS::now();
// Periodically reset own peers
if self.next_own_address_reset <= now {
self.reset_own_addresses().await.map_err(|err| Error::SocketIo("Failed to get own addresses", err))?;
self.next_own_address_reset = now + OWN_ADDRESS_RESET_INTERVAL;
}
// Periodically send peer list to peers // Periodically send peer list to peers
if self.next_peers <= now { if self.next_peers <= now {
debug!("Send peer list to all peers"); debug!("Send peer list to all peers");
@ -476,11 +465,6 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
self.table.sync(); self.table.sync();
self.traffic.sync(); self.traffic.sync();
self.peer_crypto.store(&self.peers); self.peer_crypto.store(&self.peers);
// Periodically reset own peers
if self.next_own_address_reset <= now {
self.reset_own_addresses().await.map_err(|err| Error::SocketIo("Failed to get own addresses", err))?;
self.next_own_address_reset = now + OWN_ADDRESS_RESET_INTERVAL;
}
assert!(self.buffer.is_empty()); assert!(self.buffer.is_empty());
Ok(()) Ok(())
} }
@ -513,14 +497,7 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
async fn reset_own_addresses(&mut self) -> io::Result<()> { async fn reset_own_addresses(&mut self) -> io::Result<()> {
self.own_addresses.clear(); self.own_addresses.clear();
let socket_addr = self.socket.address().await.map(mapped_addr)?; self.own_addresses.push(self.socket.address().await.map(mapped_addr)?);
// 1) Specified advertise addresses
for addr in &self.config.advertise_addresses {
self.own_addresses.push(parse_listen(addr, socket_addr.port()));
}
// 2) Address of UDP socket
self.own_addresses.push(socket_addr);
// 3) Addresses from port forwarding
if let Some(ref pfw) = self.port_forwarding { if let Some(ref pfw) = self.port_forwarding {
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());

View File

@ -6,6 +6,7 @@ use thiserror::Error;
use std::io; use std::io;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
/// Crypto init error, this is recoverable /// Crypto init error, this is recoverable
@ -51,5 +52,5 @@ pub enum Error {
Parse(&'static str), Parse(&'static str),
#[error("Name can not be resolved: {0}")] #[error("Name can not be resolved: {0}")]
NameUnresolvable(String), NameUnresolvable(String)
} }

View File

@ -4,12 +4,11 @@ use std::{
fs::{self, File}, fs::{self, File},
io::Write, io::Write,
os::unix::fs::PermissionsExt, os::unix::fs::PermissionsExt,
process::Command, process::Command
}; };
const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz"); const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz");
const SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud@.service"); const SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud@.service");
const TARGET_FILE: &[u8] = include_bytes!("../assets/vpncloud.target");
const WS_PROXY_SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud-wsproxy.service"); const WS_PROXY_SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud-wsproxy.service");
const EXAMPLE_CONFIG: &[u8] = include_bytes!("../assets/example.net.disabled"); const EXAMPLE_CONFIG: &[u8] = include_bytes!("../assets/example.net.disabled");
@ -23,10 +22,10 @@ pub fn install() -> Result<(), Error> {
env::current_exe() env::current_exe()
.and_then(|p| fs::copy(p, "/usr/bin/vpncloud")) .and_then(|p| fs::copy(p, "/usr/bin/vpncloud"))
.map_err(|e| Error::FileIo("Failed to copy binary", e))?; .map_err(|e| Error::FileIo("Failed to copy binary", e))?;
fs::set_permissions("/usr/bin/vpncloud", fs::Permissions::from_mode(0o755)) fs::set_permissions("/usr/bin/vpncloud", fs::Permissions::from_mode(755))
.map_err(|e| Error::FileIo("Failed to set permissions for binary", e))?; .map_err(|e| Error::FileIo("Failed to set permissions for binary", e))?;
fs::create_dir_all("/etc/vpncloud").map_err(|e| Error::FileIo("Failed to create config folder", e))?; fs::create_dir_all("/etc/vpncloud").map_err(|e| Error::FileIo("Failed to create config folder", e))?;
fs::set_permissions("/etc/vpncloud", fs::Permissions::from_mode(0o700)) fs::set_permissions("/etc/vpncloud", fs::Permissions::from_mode(700))
.map_err(|e| Error::FileIo("Failed to set permissions for config folder", e))?; .map_err(|e| Error::FileIo("Failed to set permissions for config folder", e))?;
File::create("/etc/vpncloud/example.net.disabled") File::create("/etc/vpncloud/example.net.disabled")
.and_then(|mut f| f.write_all(EXAMPLE_CONFIG)) .and_then(|mut f| f.write_all(EXAMPLE_CONFIG))
@ -37,9 +36,6 @@ pub fn install() -> Result<(), Error> {
File::create("/lib/systemd/system/vpncloud@.service") File::create("/lib/systemd/system/vpncloud@.service")
.and_then(|mut f| f.write_all(SERVICE_FILE)) .and_then(|mut f| f.write_all(SERVICE_FILE))
.map_err(|e| Error::FileIo("Failed to create service file", e))?; .map_err(|e| Error::FileIo("Failed to create service file", e))?;
File::create("/lib/systemd/system/vpncloud.target")
.and_then(|mut f| f.write_all(TARGET_FILE))
.map_err(|e| Error::FileIo("Failed to create service target file", e))?;
File::create("/lib/systemd/system/vpncloud-wsproxy.service") File::create("/lib/systemd/system/vpncloud-wsproxy.service")
.and_then(|mut f| f.write_all(WS_PROXY_SERVICE_FILE)) .and_then(|mut f| f.write_all(WS_PROXY_SERVICE_FILE))
.map_err(|e| Error::FileIo("Failed to create wsporxy service file", e))?; .map_err(|e| Error::FileIo("Failed to create wsporxy service file", e))?;
@ -53,8 +49,6 @@ pub fn uninstall() -> Result<(), Error> {
fs::remove_file("/usr/share/man/man1/vpncloud.1.gz").map_err(|e| Error::FileIo("Failed to remove manpage", e))?; fs::remove_file("/usr/share/man/man1/vpncloud.1.gz").map_err(|e| Error::FileIo("Failed to remove manpage", e))?;
fs::remove_file("/lib/systemd/system/vpncloud@.service") fs::remove_file("/lib/systemd/system/vpncloud@.service")
.map_err(|e| Error::FileIo("Failed to remove service file", e))?; .map_err(|e| Error::FileIo("Failed to remove service file", e))?;
fs::remove_file("/lib/systemd/system/vpncloud.target")
.map_err(|e| Error::FileIo("Failed to remove service target file", e))?;
fs::remove_file("/lib/systemd/system/vpncloud-wsproxy.service") fs::remove_file("/lib/systemd/system/vpncloud-wsproxy.service")
.map_err(|e| Error::FileIo("Failed to remove wsproxy service file", e))?; .map_err(|e| Error::FileIo("Failed to remove wsproxy service file", e))?;
fs::remove_file("/usr/bin/vpncloud").map_err(|e| Error::FileIo("Failed to remove binary", e))?; fs::remove_file("/usr/bin/vpncloud").map_err(|e| Error::FileIo("Failed to remove binary", e))?;

View File

@ -6,8 +6,7 @@
#[macro_use] extern crate serde; #[macro_use] extern crate serde;
#[macro_use] extern crate tokio; #[macro_use] extern crate tokio;
#[cfg(test)] #[cfg(test)] extern crate tempfile;
extern crate tempfile;
#[macro_use] #[macro_use]
pub mod util; pub mod util;
@ -20,8 +19,6 @@ pub mod config;
pub mod crypto; pub mod crypto;
pub mod device; pub mod device;
pub mod error; pub mod error;
#[cfg(feature = "installer")]
pub mod installer;
pub mod messages; pub mod messages;
pub mod net; pub mod net;
pub mod oldconfig; pub mod oldconfig;
@ -31,10 +28,9 @@ pub mod port_forwarding;
pub mod table; pub mod table;
pub mod traffic; pub mod traffic;
pub mod types; pub mod types;
#[cfg(feature = "wizard")] #[cfg(feature = "wizard")] pub mod wizard;
pub mod wizard; #[cfg(feature = "websocket")] pub mod wsproxy;
#[cfg(feature = "websocket")] #[cfg(feature = "installer")] pub mod installer;
pub mod wsproxy;
use structopt::StructOpt; use structopt::StructOpt;
@ -47,7 +43,7 @@ use std::{
process, process,
str::FromStr, str::FromStr,
sync::Mutex, sync::Mutex,
thread, thread
}; };
use crate::{ use crate::{
@ -65,7 +61,7 @@ use crate::{
use crate::wsproxy::ProxyConnection; use crate::wsproxy::ProxyConnection;
struct DualLogger { struct DualLogger {
file: Option<Mutex<File>>, file: Option<Mutex<File>>
} }
impl DualLogger { impl DualLogger {
@ -121,18 +117,18 @@ fn run_script(script: &str, ifname: &str) {
error!("Script returned with error: {:?}", status.code()) error!("Script returned with error: {:?}", status.code())
} }
} }
Err(e) => error!("Failed to execute script {:?}: {}", script, e), Err(e) => error!("Failed to execute script {:?}: {}", script, e)
} }
} }
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> { fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
let (ip_str, len_str) = match addr.find('/') { let (ip_str, len_str) = match addr.find('/') {
Some(pos) => (&addr[..pos], &addr[pos + 1..]), Some(pos) => (&addr[..pos], &addr[pos + 1..]),
None => (addr, "24"), None => (addr, "24")
}; };
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?; let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
if prefix_len > 32 { if prefix_len > 32 {
return Err(format!("Invalid prefix length: {}", prefix_len)); return Err(format!("Invalid prefix length: {}", prefix_len))
} }
let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?; let ip = Ipv4Addr::from_str(ip_str).map_err(|_| format!("Invalid ip address: {}", ip_str))?;
let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap()); let netmask = Ipv4Addr::from(u32::max_value().checked_shl(32 - prefix_len as u32).unwrap());
@ -237,7 +233,7 @@ async fn main() {
let args: Args = Args::from_args(); let args: Args = Args::from_args();
if args.version { if args.version {
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION")); println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
return; 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();
@ -298,7 +294,7 @@ async fn main() {
} }
} }
} }
return; return
} }
let mut config = Config::default(); let mut config = Config::default();
if let Some(ref file) = args.config { if let Some(ref file) = args.config {
@ -323,7 +319,7 @@ async fn main() {
debug!("Config: {:?}", config); debug!("Config: {:?}", config);
if config.crypto.password.is_none() && config.crypto.private_key.is_none() { 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"); error!("Either password or private key must be set in config or given as parameter");
return; return
} }
#[cfg(feature = "websocket")] #[cfg(feature = "websocket")]
if config.listen.starts_with("ws://") { if config.listen.starts_with("ws://") {
@ -332,7 +328,7 @@ async fn main() {
Type::Tap => run::<payload::Frame, _>(config, socket).await, Type::Tap => run::<payload::Frame, _>(config, socket).await,
Type::Tun => run::<payload::Packet, _>(config, socket).await Type::Tun => run::<payload::Packet, _>(config, socket).await
} }
return; return
} }
let socket = try_fail!(NetSocket::listen(&config.listen).await, "Failed to open socket {}: {}", config.listen); let socket = try_fail!(NetSocket::listen(&config.listen).await, "Failed to open socket {}: {}", config.listen);
match config.device_type { match config.device_type {

View File

@ -6,27 +6,30 @@ use crate::{
crypto::Payload, crypto::Payload,
error::Error, error::Error,
types::{NodeId, Range, RangeList, NODE_ID_BYTES}, types::{NodeId, Range, RangeList, NODE_ID_BYTES},
util::MsgBuffer, util::MsgBuffer
}; };
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::{ use std::{
io::{self, Cursor, Read, Seek, SeekFrom, Take, Write}, io::{self, Cursor, Read, Seek, SeekFrom, Take, Write},
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}
}; };
pub const MESSAGE_TYPE_DATA: u8 = 0; pub const MESSAGE_TYPE_DATA: u8 = 0;
pub const MESSAGE_TYPE_NODE_INFO: u8 = 1; pub const MESSAGE_TYPE_NODE_INFO: u8 = 1;
pub const MESSAGE_TYPE_KEEPALIVE: u8 = 2; pub const MESSAGE_TYPE_KEEPALIVE: u8 = 2;
pub const MESSAGE_TYPE_CLOSE: u8 = 0xff; pub const MESSAGE_TYPE_CLOSE: u8 = 0xff;
pub type AddrList = SmallVec<[SocketAddr; 4]>; pub type AddrList = SmallVec<[SocketAddr; 4]>;
pub type PeerList = SmallVec<[PeerInfo; 16]>; pub type PeerList = SmallVec<[PeerInfo; 16]>;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct PeerInfo { pub struct PeerInfo {
pub node_id: Option<NodeId>, pub node_id: Option<NodeId>,
pub addrs: AddrList, pub addrs: AddrList
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -35,7 +38,7 @@ pub struct NodeInfo {
pub peers: PeerList, pub peers: PeerList,
pub claims: RangeList, pub claims: RangeList,
pub peer_timeout: Option<u16>, pub peer_timeout: Option<u16>,
pub addrs: AddrList, pub addrs: AddrList
} }
impl NodeInfo { impl NodeInfo {
@ -50,7 +53,7 @@ impl NodeInfo {
let flags = r.read_u8()?; let flags = r.read_u8()?;
Self::read_addr_list_inner(r, flags) Self::read_addr_list_inner(r, flags)
} }
fn read_addr_list_inner<R: Read>(r: &mut Take<R>, flags: u8) -> Result<AddrList, io::Error> { fn read_addr_list_inner<R: Read>(r: &mut Take<R>, flags: u8) -> Result<AddrList, io::Error> {
let num_ipv4_addrs = (flags & 0x07) as usize; let num_ipv4_addrs = (flags & 0x07) as usize;
let num_ipv6_addrs = (flags & 0x38) as usize / 8; let num_ipv6_addrs = (flags & 0x38) as usize / 8;
@ -71,7 +74,7 @@ impl NodeInfo {
} }
Ok(addrs) Ok(addrs)
} }
fn decode_peer_list_part<R: Read>(r: &mut Take<R>) -> Result<PeerList, io::Error> { fn decode_peer_list_part<R: Read>(r: &mut Take<R>) -> Result<PeerList, io::Error> {
let mut peers = smallvec![]; let mut peers = smallvec![];
while r.limit() > 0 { while r.limit() > 0 {
@ -106,7 +109,7 @@ impl NodeInfo {
loop { loop {
let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?; let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?;
if part == Self::PART_END { if part == Self::PART_END {
break; break
} }
let part_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Message("Truncated message"))? as usize; let part_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Message("Truncated message"))? as usize;
let mut rp = r.take(part_len as u64); let mut rp = r.take(part_len as u64);
@ -136,7 +139,7 @@ impl NodeInfo {
} }
let node_id = match node_id { let node_id = match node_id {
Some(node_id) => node_id, Some(node_id) => node_id,
None => return Err(Error::Message("Payload without node_id")), None => return Err(Error::Message("Payload without node_id"))
}; };
Ok(Self { node_id, peers, claims, peer_timeout, addrs }) Ok(Self { node_id, peers, claims, peer_timeout, addrs })
} }
@ -152,7 +155,7 @@ impl NodeInfo {
for a in &p.addrs { for a in &p.addrs {
match a { match a {
SocketAddr::V4(addr) => addr_ipv4.push(*addr), SocketAddr::V4(addr) => addr_ipv4.push(*addr),
SocketAddr::V6(addr) => addr_ipv6.push(*addr), SocketAddr::V6(addr) => addr_ipv6.push(*addr)
} }
} }
while addr_ipv4.len() >= 8 { while addr_ipv4.len() >= 8 {
@ -187,7 +190,7 @@ impl NodeInfo {
for a in &self.addrs { for a in &self.addrs {
match a { match a {
SocketAddr::V4(addr) => addr_ipv4.push(*addr), SocketAddr::V4(addr) => addr_ipv4.push(*addr),
SocketAddr::V6(addr) => addr_ipv6.push(*addr), SocketAddr::V6(addr) => addr_ipv6.push(*addr)
} }
} }
while addr_ipv4.len() >= 8 { while addr_ipv4.len() >= 8 {
@ -210,7 +213,7 @@ impl NodeInfo {
} }
fn encode_part<F: FnOnce(&mut Cursor<&mut [u8]>) -> Result<(), io::Error>>( fn encode_part<F: FnOnce(&mut Cursor<&mut [u8]>) -> Result<(), io::Error>>(
cursor: &mut Cursor<&mut [u8]>, part: u8, f: F, cursor: &mut Cursor<&mut [u8]>, part: u8, f: F
) -> Result<(), io::Error> { ) -> Result<(), io::Error> {
cursor.write_u8(part)?; cursor.write_u8(part)?;
cursor.write_u16::<NetworkEndian>(0)?; cursor.write_u16::<NetworkEndian>(0)?;
@ -254,6 +257,7 @@ impl NodeInfo {
} }
} }
impl Payload for NodeInfo { impl Payload for NodeInfo {
fn write_to(&self, buffer: &mut MsgBuffer) { fn write_to(&self, buffer: &mut MsgBuffer) {
self.encode(buffer) self.encode(buffer)

View File

@ -2,15 +2,15 @@
// Copyright (C) 2015-2021 Dennis Schwerdel // Copyright (C) 2015-2021 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::config::DEFAULT_PORT; use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
use crate::port_forwarding::PortForwarding; use crate::port_forwarding::PortForwarding;
use crate::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
use async_trait::async_trait; use async_trait::async_trait;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
io::{self, ErrorKind}, io::{self, ErrorKind},
net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket}, net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket},
os::unix::io::AsRawFd,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
@ -40,17 +40,15 @@ pub trait Socket: Sized + Clone + Send + Sync + 'static {
async fn create_port_forwarding(&self) -> Option<PortForwarding>; async fn create_port_forwarding(&self) -> Option<PortForwarding>;
} }
pub fn parse_listen(addr: &str, default_port: u16) -> SocketAddr { pub fn parse_listen(addr: &str) -> SocketAddr {
if let Some(addr) = addr.strip_prefix("*:") { if let Some(addr) = addr.strip_prefix("*:") {
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}"); let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
} else if addr.contains(':') { } else if addr.contains(':') {
try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr) try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr)
} else if let Ok(port) = addr.parse::<u16>() {
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
} else { } else {
let ip = try_fail!(addr.parse::<IpAddr>(), "Invalid addr: {}"); let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
SocketAddr::new(ip, default_port) SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
} }
} }
@ -62,10 +60,11 @@ impl Clone for NetSocket {
} }
} }
#[async_trait] #[async_trait]
impl Socket for NetSocket { impl Socket for NetSocket {
async fn listen(addr: &str) -> Result<Self, io::Error> { async fn listen(addr: &str) -> Result<Self, io::Error> {
let addr = parse_listen(addr, DEFAULT_PORT); let addr = parse_listen(addr);
Ok(NetSocket(UdpSocket::bind(addr)?)) Ok(NetSocket(UdpSocket::bind(addr)?))
} }
@ -143,10 +142,11 @@ impl MockSocket {
} }
} }
#[async_trait] #[async_trait]
impl Socket for MockSocket { impl Socket for MockSocket {
async fn listen(addr: &str) -> Result<Self, io::Error> { async fn listen(addr: &str) -> Result<Self, io::Error> {
Ok(Self::new(parse_listen(addr, DEFAULT_PORT))) Ok(Self::new(parse_listen(addr)))
} }
async fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> { async fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {

View File

@ -13,7 +13,7 @@ pub enum OldCryptoMethod {
#[serde(rename = "aes256")] #[serde(rename = "aes256")]
AES256, AES256,
#[serde(rename = "aes128")] #[serde(rename = "aes128")]
AES128, AES128
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
@ -57,7 +57,7 @@ pub struct OldConfigFile {
#[serde(alias = "statsd-prefix")] #[serde(alias = "statsd-prefix")]
pub statsd_prefix: Option<String>, pub statsd_prefix: Option<String>,
pub user: Option<String>, pub user: Option<String>,
pub group: Option<String>, pub group: Option<String>
} }
impl OldConfigFile { impl OldConfigFile {
@ -89,7 +89,7 @@ impl OldConfigFile {
interval: self.beacon_interval, interval: self.beacon_interval,
load: self.beacon_load, load: self.beacon_load,
store: self.beacon_store, store: self.beacon_store,
password: self.shared_key.clone(), password: self.shared_key.clone()
}), }),
claims: self.subnets, claims: self.subnets,
crypto: CryptoConfig { crypto: CryptoConfig {
@ -97,7 +97,7 @@ impl OldConfigFile {
password: Some(self.shared_key.unwrap_or_else(|| "none".to_string())), password: Some(self.shared_key.unwrap_or_else(|| "none".to_string())),
private_key: None, private_key: None,
public_key: None, public_key: None,
trusted_keys: vec![], trusted_keys: vec![]
}, },
device: Some(ConfigFileDevice { device: Some(ConfigFileDevice {
fix_rp_filter: None, fix_rp_filter: None,
@ -110,7 +110,6 @@ impl OldConfigFile {
ifdown: self.ifdown, ifdown: self.ifdown,
ifup: self.ifup, ifup: self.ifup,
ip: None, ip: None,
advertise_addresses: None,
keepalive: self.keepalive, keepalive: self.keepalive,
listen: self.listen.or(self.port.map(|p| format!("{}", p))), listen: self.listen.or(self.port.map(|p| format!("{}", p))),
mode: self.mode, mode: self.mode,
@ -123,7 +122,7 @@ impl OldConfigFile {
switch_timeout: self.dst_timeout, switch_timeout: self.dst_timeout,
user: self.user, user: self.user,
hook: None, hook: None,
hooks: HashMap::new(), hooks: HashMap::new()
} }
} }
} }

View File

@ -43,7 +43,7 @@ impl Protocol for Frame {
// treat vlan id 0x000 as untagged // treat vlan id 0x000 as untagged
src.copy_within(2..8, 0); src.copy_within(2..8, 0);
dst.copy_within(2..8, 0); dst.copy_within(2..8, 0);
return Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 })); return Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 }))
} }
Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 })) Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 }))
} else { } else {
@ -52,6 +52,7 @@ impl Protocol for Frame {
} }
} }
#[test] #[test]
async fn decode_frame_without_vlan() { async fn decode_frame_without_vlan() {
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8];
@ -92,13 +93,13 @@ impl Protocol for Packet {
fn parse(data: &[u8]) -> Result<(Address, Address), Error> { fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
// HOT PATH // HOT PATH
if data.is_empty() { if data.is_empty() {
return Err(Error::Parse("Empty header")); return Err(Error::Parse("Empty header"))
} }
let version = data[0] >> 4; let version = data[0] >> 4;
match version { match version {
4 => { 4 => {
if data.len() < 20 { if data.len() < 20 {
return Err(Error::Parse("Truncated IPv4 header")); return Err(Error::Parse("Truncated IPv4 header"))
} }
let src = Address::read_from_fixed(&data[12..], 4)?; let src = Address::read_from_fixed(&data[12..], 4)?;
let dst = Address::read_from_fixed(&data[16..], 4)?; let dst = Address::read_from_fixed(&data[16..], 4)?;
@ -106,17 +107,18 @@ impl Protocol for Packet {
} }
6 => { 6 => {
if data.len() < 40 { if data.len() < 40 {
return Err(Error::Parse("Truncated IPv6 header")); return Err(Error::Parse("Truncated IPv6 header"))
} }
let src = Address::read_from_fixed(&data[8..], 16)?; let src = Address::read_from_fixed(&data[8..], 16)?;
let dst = Address::read_from_fixed(&data[24..], 16)?; let dst = Address::read_from_fixed(&data[24..], 16)?;
Ok((src, dst)) Ok((src, dst))
} }
_ => Err(Error::Parse("Invalid IP protocol version")), _ => Err(Error::Parse("Invalid IP protocol version"))
} }
} }
} }
#[test] #[test]
async fn decode_ipv4_packet() { async fn decode_ipv4_packet() {
let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2];
@ -129,7 +131,7 @@ async fn decode_ipv4_packet() {
async fn decode_ipv6_packet() { async fn decode_ipv6_packet() {
let data = [ let data = [
0x60, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 0x60, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5,
4, 3, 2, 1, 4, 3, 2, 1
]; ];
let (src, dst) = Packet::parse(&data).unwrap(); let (src, dst) = Packet::parse(&data).unwrap();
assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6], len: 16 }); assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6], len: 16 });
@ -156,4 +158,4 @@ async fn decode_invalid_packet() {
4, 3, 2 4, 3, 2
]) ])
.is_err()); .is_err());
} }

View File

@ -11,7 +11,7 @@ pub struct EpollWait {
event: libc::epoll_event, event: libc::epoll_event,
socket: RawFd, socket: RawFd,
device: RawFd, device: RawFd,
timeout: u32, timeout: u32
} }
impl EpollWait { impl EpollWait {
@ -27,14 +27,14 @@ impl EpollWait {
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, 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) };
if res == -1 { if res == -1 {
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, device, timeout })
@ -63,7 +63,7 @@ impl Iterator for EpollWait {
unreachable!() unreachable!()
} }
} }
_ => unreachable!(), _ => unreachable!()
}) })
} }
} }

View File

@ -8,11 +8,12 @@ mod epoll;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
pub use self::epoll::EpollWait as WaitImpl; pub use self::epoll::EpollWait as WaitImpl;
use std::io; use std::io;
pub enum WaitResult { pub enum WaitResult {
Timeout, Timeout,
Socket, Socket,
Device, Device,
Error(io::Error), Error(io::Error)
} }

View File

@ -19,7 +19,7 @@ mod internal {
pub internal_addr: SocketAddrV4, pub internal_addr: SocketAddrV4,
pub external_addr: SocketAddrV4, pub external_addr: SocketAddrV4,
gateway: Gateway, gateway: Gateway,
pub next_extension: Option<Time>, pub next_extension: Option<Time>
} }
impl PortForwarding { impl PortForwarding {
@ -32,11 +32,11 @@ mod internal {
if err.kind() == io::ErrorKind::WouldBlock { if err.kind() == io::ErrorKind::WouldBlock {
// Why this code? // Why this code?
info!("Port-forwarding: no router found"); info!("Port-forwarding: no router found");
return None; return None
} }
} }
error!("Port-forwarding: failed to find router: {}", err); error!("Port-forwarding: failed to find router: {}", err);
return None; return None
} }
}; };
debug!("Port-forwarding: found router at {}", gateway.addr); debug!("Port-forwarding: found router at {}", gateway.addr);
@ -46,7 +46,7 @@ mod internal {
Ok(ip) => ip, Ok(ip) => ip,
Err(err) => { Err(err) => {
error!("Port-forwarding: failed to obtain external IP: {}", err); error!("Port-forwarding: failed to obtain external IP: {}", err);
return None; return None
} }
}; };
if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) { if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) {
@ -64,19 +64,19 @@ mod internal {
fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> { fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
if let Ok(a) = Self::get_forwarding(gateway, addr, port) { if let Ok(a) = Self::get_forwarding(gateway, addr, port) {
return Ok(a); return Ok(a)
} }
if let Ok(a) = Self::get_forwarding(gateway, addr, 0) { if let Ok(a) = Self::get_forwarding(gateway, addr, 0) {
return Ok(a); return Ok(a)
} }
for i in 1..5 { for i in 1..5 {
if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) { if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) {
return Ok(a); return Ok(a)
} }
} }
for _ in 0..5 { for _ in 0..5 {
if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) { if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) {
return Ok(a); return Ok(a)
} }
} }
warn!("Failed to activate port forwarding"); warn!("Failed to activate port forwarding");
@ -125,20 +125,20 @@ mod internal {
pub fn check_extend(&mut self) { pub fn check_extend(&mut self) {
if let Some(deadline) = self.next_extension { if let Some(deadline) = self.next_extension {
if deadline > SystemTimeSource::now() { if deadline > SystemTimeSource::now() {
return; return
} }
} else { } else {
return; return
} }
match self.gateway.add_port( match self.gateway.add_port(
PortMappingProtocol::UDP, PortMappingProtocol::UDP,
self.external_addr.port(), self.external_addr.port(),
self.internal_addr, self.internal_addr,
LEASE_TIME, LEASE_TIME,
DESCRIPTION, DESCRIPTION
) { ) {
Ok(()) => debug!("Port-forwarding: extended port forwarding"), Ok(()) => debug!("Port-forwarding: extended port forwarding"),
Err(err) => debug!("Port-forwarding: failed to extend port forwarding: {}", err), Err(err) => debug!("Port-forwarding: failed to extend port forwarding: {}", err)
}; };
self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60); self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60);
} }
@ -146,7 +146,7 @@ mod internal {
fn deactivate(&self) { fn deactivate(&self) {
match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) { match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) {
Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"), Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"),
Err(err) => debug!("Port-forwarding: failed to deactivate port forwarding: {}", err), Err(err) => debug!("Port-forwarding: failed to deactivate port forwarding: {}", err)
} }
} }

View File

@ -4,25 +4,27 @@
use fnv::FnvHasher; use fnv::FnvHasher;
use std::{ use std::{
cmp::min, collections::HashMap, hash::BuildHasherDefault, io, io::Write, marker::PhantomData, net::SocketAddr, cmp::min, collections::HashMap, hash::BuildHasherDefault, io, io::Write, marker::PhantomData, net::SocketAddr
}; };
use crate::{ use crate::{
types::{Address, Range, RangeList}, types::{Address, Range, RangeList},
util::{addr_nice, Duration, Time, TimeSource}, util::{addr_nice, Duration, Time, TimeSource}
}; };
type Hash = BuildHasherDefault<FnvHasher>; type Hash = BuildHasherDefault<FnvHasher>;
struct CacheValue { struct CacheValue {
peer: SocketAddr, peer: SocketAddr,
timeout: Time, timeout: Time
} }
struct ClaimEntry { struct ClaimEntry {
peer: SocketAddr, peer: SocketAddr,
claim: Range, claim: Range,
timeout: Time, timeout: Time
} }
pub struct ClaimTable<TS: TimeSource> { pub struct ClaimTable<TS: TimeSource> {
@ -30,7 +32,7 @@ pub struct ClaimTable<TS: TimeSource> {
cache_timeout: Duration, cache_timeout: Duration,
claims: Vec<ClaimEntry>, claims: Vec<ClaimEntry>,
claim_timeout: Duration, claim_timeout: Duration,
_dummy: PhantomData<TS>, _dummy: PhantomData<TS>
} }
impl<TS: TimeSource> ClaimTable<TS> { impl<TS: TimeSource> ClaimTable<TS> {
@ -55,7 +57,7 @@ impl<TS: TimeSource> ClaimTable<TS> {
entry.timeout = TS::now() + self.claim_timeout as Time; entry.timeout = TS::now() + self.claim_timeout as Time;
claims.swap_remove(pos); claims.swap_remove(pos);
if claims.is_empty() { if claims.is_empty() {
break; break
} }
} else { } else {
entry.timeout = 0 entry.timeout = 0
@ -90,7 +92,7 @@ impl<TS: TimeSource> ClaimTable<TS> {
pub fn lookup(&mut self, addr: Address) -> Option<SocketAddr> { pub fn lookup(&mut self, addr: Address) -> Option<SocketAddr> {
// HOT PATH // HOT PATH
if let Some(entry) = self.cache.get(&addr) { if let Some(entry) = self.cache.get(&addr) {
return Some(entry.peer); return Some(entry.peer)
} }
// COLD PATH // COLD PATH
let mut found = None; let mut found = None;
@ -102,11 +104,11 @@ impl<TS: TimeSource> ClaimTable<TS> {
} }
} }
if let Some(entry) = found { if let Some(entry) = found {
self.cache.insert( self.cache.insert(addr, CacheValue {
addr, peer: entry.peer,
CacheValue { peer: entry.peer, timeout: min(TS::now() + self.cache_timeout as Time, entry.timeout) }, timeout: min(TS::now() + self.cache_timeout as Time, entry.timeout)
); });
return Some(entry.peer); return Some(entry.peer)
} }
None None
} }
@ -153,4 +155,4 @@ impl<TS: TimeSource> ClaimTable<TS> {
} }
} }
// TODO: test // TODO: test

View File

@ -8,8 +8,8 @@ use std::{
net::SocketAddr, net::SocketAddr,
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Once, Once
}, }
}; };
pub use crate::{ pub use crate::{
@ -19,9 +19,10 @@ pub use crate::{
net::MockSocket, net::MockSocket,
payload::{Frame, Packet, Protocol}, payload::{Frame, Packet, Protocol},
types::Range, types::Range,
util::{MockTimeSource, Time, TimeSource}, util::{MockTimeSource, Time, TimeSource}
}; };
static INIT_LOGGER: Once = Once::new(); static INIT_LOGGER: Once = Once::new();
pub fn init_debug_logger() { pub fn init_debug_logger() {
@ -60,18 +61,20 @@ impl log::Log for DebugLogger {
} }
} }
type TestNode<P> = GenericCloud<MockDevice, P, MockSocket, MockTimeSource>; type TestNode<P> = GenericCloud<MockDevice, P, MockSocket, MockTimeSource>;
pub struct Simulator<P: Protocol> { pub struct Simulator<P: Protocol> {
next_port: u16, next_port: u16,
nodes: HashMap<SocketAddr, TestNode<P>>, nodes: HashMap<SocketAddr, TestNode<P>>,
messages: VecDeque<(SocketAddr, SocketAddr, Vec<u8>)>, messages: VecDeque<(SocketAddr, SocketAddr, Vec<u8>)>
} }
pub type TapSimulator = Simulator<Frame>; pub type TapSimulator = Simulator<Frame>;
#[allow(dead_code)] #[allow(dead_code)]
pub type TunSimulator = Simulator<Packet>; pub type TunSimulator = Simulator<Packet>;
impl<P: Protocol> Simulator<P> { impl<P: Protocol> Simulator<P> {
pub fn new() -> Self { pub fn new() -> Self {
init_debug_logger(); init_debug_logger();

View File

@ -5,9 +5,4 @@
mod common; mod common;
mod nat; mod nat;
mod payload; mod payload;
mod peers; mod peers;
#[test]
async fn test_time_format() {
assert!(time::OffsetDateTime::try_now_local().is_ok());
}

View File

@ -6,13 +6,13 @@ use std::{
collections::HashMap, collections::HashMap,
io::{self, Write}, io::{self, Write},
net::SocketAddr, net::SocketAddr,
ops::AddAssign, ops::AddAssign
}; };
use super::{ use super::{
engine::common::{Hash, STATS_INTERVAL}, engine::common::{Hash, STATS_INTERVAL},
types::Address, types::Address,
util::{addr_nice, Bytes}, util::{addr_nice, Bytes}
}; };
@ -26,7 +26,7 @@ pub struct TrafficEntry {
pub in_packets_total: usize, pub in_packets_total: usize,
pub in_bytes: u64, pub in_bytes: u64,
pub in_packets: usize, pub in_packets: usize,
pub idle_periods: usize, pub idle_periods: usize
} }
impl AddAssign<&TrafficEntry> for TrafficEntry { impl AddAssign<&TrafficEntry> for TrafficEntry {
@ -72,11 +72,12 @@ impl TrafficEntry {
} }
} }
#[derive(Default)] #[derive(Default)]
pub struct TrafficStats { pub struct TrafficStats {
peers: HashMap<SocketAddr, TrafficEntry, Hash>, peers: HashMap<SocketAddr, TrafficEntry, Hash>,
payload: HashMap<(Address, Address), TrafficEntry, Hash>, payload: HashMap<(Address, Address), TrafficEntry, Hash>,
pub dropped: TrafficEntry, pub dropped: TrafficEntry
} }
impl TrafficStats { impl TrafficStats {

View File

@ -4,7 +4,7 @@
use crate::{ use crate::{
error::Error, error::Error,
util::{bytes_to_hex, Encoder}, util::{bytes_to_hex, Encoder}
}; };
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -13,17 +13,18 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
io::{Read, Write}, io::{Read, Write},
net::{Ipv4Addr, Ipv6Addr}, net::{Ipv4Addr, Ipv6Addr},
str::FromStr, str::FromStr
}; };
pub const NODE_ID_BYTES: usize = 16; pub const NODE_ID_BYTES: usize = 16;
pub type NodeId = [u8; NODE_ID_BYTES]; pub type NodeId = [u8; NODE_ID_BYTES];
#[derive(Eq, Clone, Copy)] #[derive(Eq, Clone, Copy)]
pub struct Address { pub struct Address {
pub data: [u8; 16], pub data: [u8; 16],
pub len: u8, pub len: u8
} }
impl Address { impl Address {
@ -36,7 +37,7 @@ impl Address {
#[inline] #[inline]
pub fn read_from_fixed<R: Read>(mut r: R, len: u8) -> Result<Address, Error> { pub fn read_from_fixed<R: Read>(mut r: R, len: u8) -> Result<Address, Error> {
if len > 16 { if len > 16 {
return Err(Error::Parse("Invalid address, too long")); return Err(Error::Parse("Invalid address, too long"))
} }
let mut data = [0; 16]; let mut data = [0; 16];
r.read_exact(&mut data[..len as usize]).map_err(|_| Error::Parse("Address too short"))?; r.read_exact(&mut data[..len as usize]).map_err(|_| Error::Parse("Address too short"))?;
@ -56,6 +57,7 @@ impl Address {
} }
} }
impl PartialEq for Address { impl PartialEq for Address {
#[inline] #[inline]
fn eq(&self, rhs: &Self) -> bool { fn eq(&self, rhs: &Self) -> bool {
@ -63,6 +65,7 @@ impl PartialEq for Address {
} }
} }
impl Hash for Address { impl Hash for Address {
#[inline] #[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) { fn hash<H: Hasher>(&self, hasher: &mut H) {
@ -70,6 +73,7 @@ impl Hash for Address {
} }
} }
impl fmt::Display for Address { impl fmt::Display for Address {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let d = &self.data; let d = &self.data;
@ -104,7 +108,7 @@ impl FromStr for Address {
let ip = addr.octets(); let ip = addr.octets();
let mut res = [0; 16]; let mut res = [0; 16];
res[0..4].copy_from_slice(&ip); res[0..4].copy_from_slice(&ip);
return Ok(Address { data: res, len: 4 }); return Ok(Address { data: res, len: 4 })
} }
if let Ok(addr) = Ipv6Addr::from_str(text) { if let Ok(addr) = Ipv6Addr::from_str(text) {
let segments = addr.segments(); let segments = addr.segments();
@ -112,7 +116,7 @@ impl FromStr for Address {
for i in 0..8 { for i in 0..8 {
Encoder::write_u16(segments[i], &mut res[2 * i..]); Encoder::write_u16(segments[i], &mut res[2 * i..]);
} }
return Ok(Address { data: res, len: 16 }); return Ok(Address { data: res, len: 16 })
} }
let parts: SmallVec<[&str; 10]> = text.split(':').collect(); let parts: SmallVec<[&str; 10]> = text.split(':').collect();
if parts.len() == 6 { if parts.len() == 6 {
@ -120,16 +124,17 @@ impl FromStr for Address {
for i in 0..6 { for i in 0..6 {
bytes[i] = u8::from_str_radix(parts[i], 16).map_err(|_| Error::Parse("Failed to parse mac"))?; bytes[i] = u8::from_str_radix(parts[i], 16).map_err(|_| Error::Parse("Failed to parse mac"))?;
} }
return Ok(Address { data: bytes, len: 6 }); return Ok(Address { data: bytes, len: 6 })
} }
Err(Error::Parse("Failed to parse address")) Err(Error::Parse("Failed to parse address"))
} }
} }
#[derive(PartialEq, Eq, Hash, Clone, Copy)] #[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct Range { pub struct Range {
pub base: Address, pub base: Address,
pub prefix_len: u8, pub prefix_len: u8
} }
pub type RangeList = SmallVec<[Range; 4]>; pub type RangeList = SmallVec<[Range; 4]>;
@ -137,14 +142,14 @@ pub type RangeList = SmallVec<[Range; 4]>;
impl Range { impl Range {
pub fn matches(&self, addr: Address) -> bool { pub fn matches(&self, addr: Address) -> bool {
if self.base.len != addr.len { if self.base.len != addr.len {
return false; return false
} }
let mut match_len = 0; let mut match_len = 0;
for i in 0..addr.len as usize { for i in 0..addr.len as usize {
let m = addr.data[i] ^ self.base.data[i]; let m = addr.data[i] ^ self.base.data[i];
match_len += m.leading_zeros() as u8; match_len += m.leading_zeros() as u8;
if m != 0 { if m != 0 {
break; break
} }
} }
match_len >= self.prefix_len match_len >= self.prefix_len
@ -170,7 +175,7 @@ impl FromStr for Range {
fn from_str(text: &str) -> Result<Self, Self::Err> { fn from_str(text: &str) -> Result<Self, Self::Err> {
let pos = match text.find('/') { let pos = match text.find('/') {
Some(pos) => pos, Some(pos) => pos,
None => return Err(Error::Parse("Invalid range format")), None => return Err(Error::Parse("Invalid range format"))
}; };
let prefix_len = u8::from_str(&text[pos + 1..]).map_err(|_| Error::Parse("Failed to parse prefix length"))?; let prefix_len = u8::from_str(&text[pos + 1..]).map_err(|_| Error::Parse("Failed to parse prefix length"))?;
let base = Address::from_str(&text[..pos])?; let base = Address::from_str(&text[..pos])?;
@ -190,6 +195,7 @@ impl fmt::Debug for Range {
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum Mode { pub enum Mode {
#[serde(rename = "normal")] #[serde(rename = "normal")]
@ -199,7 +205,7 @@ pub enum Mode {
#[serde(rename = "switch")] #[serde(rename = "switch")]
Switch, Switch,
#[serde(rename = "router")] #[serde(rename = "router")]
Router, Router
} }
impl fmt::Display for Mode { impl fmt::Display for Mode {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
@ -207,7 +213,7 @@ impl fmt::Display for Mode {
Mode::Normal => write!(formatter, "normal"), Mode::Normal => write!(formatter, "normal"),
Mode::Hub => write!(formatter, "hub"), Mode::Hub => write!(formatter, "hub"),
Mode::Switch => write!(formatter, "switch"), Mode::Switch => write!(formatter, "switch"),
Mode::Router => write!(formatter, "router"), Mode::Router => write!(formatter, "router")
} }
} }
} }
@ -220,11 +226,12 @@ impl FromStr for Mode {
"hub" => Self::Hub, "hub" => Self::Hub,
"switch" => Self::Switch, "switch" => Self::Switch,
"router" => Self::Router, "router" => Self::Router,
_ => return Err("Unknown mode"), _ => return Err("Unknown mode")
}) })
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -32,7 +32,7 @@ fn configure_connectivity(config: &mut Config, mode: usize, theme: &ColorfulThem
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Peer addresses (comma separated)") .with_prompt("Peer addresses (comma separated)")
.default(config.peers.join(",")) .default(config.peers.join(","))
.interact_text()?, .interact_text()?
); );
if mode >= MODE_ADVANCED { if mode >= MODE_ADVANCED {
config.port_forwarding = Confirm::with_theme(theme) config.port_forwarding = Confirm::with_theme(theme)
@ -41,12 +41,6 @@ fn configure_connectivity(config: &mut Config, mode: usize, theme: &ColorfulThem
.interact()?; .interact()?;
} }
if mode == MODE_EXPERT { if mode == MODE_EXPERT {
config.advertise_addresses = str_list(
Input::with_theme(theme)
.with_prompt("Advertise addresses (comma separated)")
.default(config.advertise_addresses.join(","))
.interact_text()?,
);
config.peer_timeout = Input::with_theme(theme) config.peer_timeout = Input::with_theme(theme)
.with_prompt("Peer timeout (in seconds)") .with_prompt("Peer timeout (in seconds)")
.default(config.peer_timeout) .default(config.peer_timeout)
@ -60,11 +54,12 @@ fn configure_connectivity(config: &mut Config, mode: usize, theme: &ColorfulThem
Ok(()) Ok(())
} }
fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> { fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
if (config.crypto.password.is_some() || config.crypto.private_key.is_some()) if (config.crypto.password.is_some() || config.crypto.private_key.is_some())
&& !Confirm::with_theme(theme).with_prompt("Create new crypto config?").default(false).interact()? && !Confirm::with_theme(theme).with_prompt("Create new crypto config?").default(false).interact()?
{ {
return Ok(()); return Ok(())
} }
let mut use_password = true; let mut use_password = true;
if mode >= MODE_ADVANCED { if mode >= MODE_ADVANCED {
@ -80,7 +75,7 @@ fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
Password::with_theme(theme) Password::with_theme(theme)
.with_prompt("Password") .with_prompt("Password")
.with_confirmation("Confirm password", "Passwords do not match") .with_confirmation("Confirm password", "Passwords do not match")
.interact()?, .interact()?
); );
config.crypto.private_key = None; config.crypto.private_key = None;
config.crypto.public_key = None; config.crypto.public_key = None;
@ -118,13 +113,13 @@ fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
info!("Public key: {}", pub_key); info!("Public key: {}", pub_key);
(priv_key, pub_key) (priv_key, pub_key)
} }
_ => unreachable!(), _ => unreachable!()
}; };
config.crypto.trusted_keys = str_list( config.crypto.trusted_keys = str_list(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Trusted keys (public keys, comma separated)") .with_prompt("Trusted keys (public keys, comma separated)")
.default(pub_key.clone()) .default(pub_key.clone())
.interact_text()?, .interact_text()?
); );
config.crypto.private_key = Some(priv_key); config.crypto.private_key = Some(priv_key);
config.crypto.public_key = Some(pub_key); config.crypto.public_key = Some(pub_key);
@ -138,7 +133,7 @@ fn configure_crypto(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
("Unencrypted (dangerous)", unencrypted), ("Unencrypted (dangerous)", unencrypted),
("AES-128 in GCM mode", allowed_algos.contains(&&aead::AES_128_GCM)), ("AES-128 in GCM mode", allowed_algos.contains(&&aead::AES_128_GCM)),
("AES-256 in GCM mode", allowed_algos.contains(&&aead::AES_256_GCM)), ("AES-256 in GCM mode", allowed_algos.contains(&&aead::AES_256_GCM)),
("ChaCha20-Poly1305 (RFC 7539)", allowed_algos.contains(&&aead::CHACHA20_POLY1305)), ("ChaCha20-Poly1305 (RFC 7539)", allowed_algos.contains(&&aead::CHACHA20_POLY1305))
]) ])
.interact()?; .interact()?;
config.crypto.algorithms = vec![]; config.crypto.algorithms = vec![];
@ -161,7 +156,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
{ {
0 => device::Type::Tun, 0 => device::Type::Tun,
1 => device::Type::Tap, 1 => device::Type::Tap,
_ => unreachable!(), _ => unreachable!()
} }
} }
if mode == MODE_EXPERT { if mode == MODE_EXPERT {
@ -171,7 +166,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Device path (empty for default)") .with_prompt("Device path (empty for default)")
.default(config.device_path.as_ref().cloned().unwrap_or_default()) .default(config.device_path.as_ref().cloned().unwrap_or_default())
.interact_text()?, .interact_text()?
); );
config.fix_rp_filter = Confirm::with_theme(theme) config.fix_rp_filter = Confirm::with_theme(theme)
.with_prompt("Automatically fix insecure rp_filter settings") .with_prompt("Automatically fix insecure rp_filter settings")
@ -184,7 +179,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
Mode::Normal => 0, Mode::Normal => 0,
Mode::Router => 1, Mode::Router => 1,
Mode::Switch => 2, Mode::Switch => 2,
Mode::Hub => 3, Mode::Hub => 3
}) })
.interact()? .interact()?
{ {
@ -192,7 +187,7 @@ fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
1 => Mode::Router, 1 => Mode::Router,
2 => Mode::Switch, 2 => Mode::Switch,
3 => Mode::Hub, 3 => Mode::Hub,
_ => unreachable!(), _ => unreachable!()
}; };
if config.mode == Mode::Switch { if config.mode == Mode::Switch {
config.switch_timeout = Input::with_theme(theme) config.switch_timeout = Input::with_theme(theme)
@ -210,7 +205,7 @@ fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme)
.with_prompt("Virtual IP address (e.g. 10.0.0.1, leave empty for none)") .with_prompt("Virtual IP address (e.g. 10.0.0.1, leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.ip.as_ref().cloned().unwrap_or_default()) .default(config.ip.as_ref().cloned().unwrap_or_default())
.interact_text()?, .interact_text()?
); );
if config.device_type == device::Type::Tun { if config.device_type == device::Type::Tun {
if mode >= MODE_ADVANCED { if mode >= MODE_ADVANCED {
@ -225,7 +220,7 @@ fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme)
.with_prompt("Claim additional addresses (e.g. 10.0.0.0/24, comma separated, leave empty for none)") .with_prompt("Claim additional addresses (e.g. 10.0.0.0/24, comma separated, leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.claims.join(",")) .default(config.claims.join(","))
.interact_text()?, .interact_text()?
); );
} }
} else { } else {
@ -237,14 +232,14 @@ fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme)
.with_prompt("Interface setup command (leave empty for none)") .with_prompt("Interface setup command (leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.ifup.as_ref().cloned().unwrap_or_default()) .default(config.ifup.as_ref().cloned().unwrap_or_default())
.interact_text()?, .interact_text()?
); );
config.ifdown = str_opt( config.ifdown = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Interface tear down command (leave empty for none)") .with_prompt("Interface tear down command (leave empty for none)")
.allow_empty(true) .allow_empty(true)
.default(config.ifdown.as_ref().cloned().unwrap_or_default()) .default(config.ifdown.as_ref().cloned().unwrap_or_default())
.interact_text()?, .interact_text()?
); );
} }
Ok(()) Ok(())
@ -272,20 +267,24 @@ fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.interact()? .interact()?
{ {
0 => None, 0 => None,
1 => Some( 1 => {
Input::with_theme(theme) Some(
.with_prompt("File path") Input::with_theme(theme)
.default(config.beacon_store.clone().unwrap_or_default()) .with_prompt("File path")
.interact_text()?, .default(config.beacon_store.clone().unwrap_or_default())
), .interact_text()?
2 => Some(format!( )
"|{}", }
Input::<String>::with_theme(theme) 2 => {
.with_prompt("Command") Some(format!(
.default(config.beacon_store.clone().unwrap_or_default().trim_start_matches('|').to_string()) "|{}",
.interact_text()? Input::<String>::with_theme(theme)
)), .with_prompt("Command")
_ => unreachable!(), .default(config.beacon_store.clone().unwrap_or_default().trim_start_matches('|').to_string())
.interact_text()?
))
}
_ => unreachable!()
}; };
config.beacon_load = match Select::with_theme(theme) config.beacon_load = match Select::with_theme(theme)
.with_prompt("How to load beacons") .with_prompt("How to load beacons")
@ -302,20 +301,24 @@ fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.interact()? .interact()?
{ {
0 => None, 0 => None,
1 => Some( 1 => {
Input::with_theme(theme) Some(
.with_prompt("File path") Input::with_theme(theme)
.default(config.beacon_load.clone().unwrap_or_default()) .with_prompt("File path")
.interact_text()?, .default(config.beacon_load.clone().unwrap_or_default())
), .interact_text()?
2 => Some(format!( )
"|{}", }
Input::<String>::with_theme(theme) 2 => {
.with_prompt("Command") Some(format!(
.default(config.beacon_load.clone().unwrap_or_default().trim_start_matches('|').to_string()) "|{}",
.interact_text()? Input::<String>::with_theme(theme)
)), .with_prompt("Command")
_ => unreachable!(), .default(config.beacon_load.clone().unwrap_or_default().trim_start_matches('|').to_string())
.interact_text()?
))
}
_ => unreachable!()
}; };
config.beacon_interval = Input::with_theme(theme) config.beacon_interval = Input::with_theme(theme)
.with_prompt("Beacon interval (in seconds)") .with_prompt("Beacon interval (in seconds)")
@ -326,7 +329,7 @@ fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.with_prompt("Beacon password (leave empty for none)") .with_prompt("Beacon password (leave empty for none)")
.with_confirmation("Confirm password", "Passwords do not match") .with_confirmation("Confirm password", "Passwords do not match")
.allow_empty_password(true) .allow_empty_password(true)
.interact()?, .interact()?
); );
} }
Ok(()) Ok(())
@ -339,7 +342,7 @@ fn configure_stats(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
.with_prompt("Write stats to file (empty to disable)") .with_prompt("Write stats to file (empty to disable)")
.default(config.stats_file.clone().unwrap_or_default()) .default(config.stats_file.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
} }
if mode == MODE_EXPERT { if mode == MODE_EXPERT {
@ -353,14 +356,14 @@ fn configure_stats(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
.with_prompt("Statsd server URL") .with_prompt("Statsd server URL")
.default(config.statsd_server.clone().unwrap_or_default()) .default(config.statsd_server.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
config.statsd_prefix = str_opt( config.statsd_prefix = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Statsd prefix") .with_prompt("Statsd prefix")
.default(config.statsd_prefix.clone().unwrap_or_default()) .default(config.statsd_prefix.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
} else { } else {
config.statsd_server = None; config.statsd_server = None;
@ -376,21 +379,21 @@ fn configure_process(config: &mut Config, mode: usize, theme: &ColorfulTheme) ->
.with_prompt("Run as different user (empty to disable)") .with_prompt("Run as different user (empty to disable)")
.default(config.user.clone().unwrap_or_default()) .default(config.user.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
config.group = str_opt( config.group = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Run as different group (empty to disable)") .with_prompt("Run as different group (empty to disable)")
.default(config.group.clone().unwrap_or_default()) .default(config.group.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
config.pid_file = str_opt( config.pid_file = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt("Write process id to file (empty to disable)") .with_prompt("Write process id to file (empty to disable)")
.default(config.pid_file.clone().unwrap_or_default()) .default(config.pid_file.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
} }
Ok(()) Ok(())
@ -408,7 +411,7 @@ fn configure_hooks(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
.with_prompt("Command to execute for all events (empty to disable)") .with_prompt("Command to execute for all events (empty to disable)")
.default(config.hook.clone().unwrap_or_default()) .default(config.hook.clone().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
); );
let mut hooks: HashMap<String, String> = Default::default(); let mut hooks: HashMap<String, String> = Default::default();
for event in &[ for event in &[
@ -418,14 +421,14 @@ fn configure_hooks(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> R
"device_setup", "device_setup",
"device_configured", "device_configured",
"vpn_started", "vpn_started",
"vpn_shutdown", "vpn_shutdown"
] { ] {
if let Some(cmd) = str_opt( if let Some(cmd) = str_opt(
Input::with_theme(theme) Input::with_theme(theme)
.with_prompt(format!("Command to execute for event '{}' (empty to disable)", event)) .with_prompt(format!("Command to execute for event '{}' (empty to disable)", event))
.default(config.hooks.get(*event).cloned().unwrap_or_default()) .default(config.hooks.get(*event).cloned().unwrap_or_default())
.allow_empty(true) .allow_empty(true)
.interact_text()?, .interact_text()?
) { ) {
hooks.insert(event.to_string(), cmd); hooks.insert(event.to_string(), cmd);
} }
@ -467,7 +470,7 @@ pub fn configure(name: Option<String>) -> Result<(), io::Error> {
config.merge_file(config_file); config.merge_file(config_file);
} }
if file.parent().unwrap().metadata()?.permissions().readonly() { if file.parent().unwrap().metadata()?.permissions().readonly() {
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "Config file not writable")); return Err(io::Error::new(io::ErrorKind::PermissionDenied, "Config file not writable"))
} }
loop { loop {
@ -486,7 +489,7 @@ pub fn configure(name: Option<String>) -> Result<(), io::Error> {
configure_process(&mut config, mode, &theme)?; configure_process(&mut config, mode, &theme)?;
configure_hooks(&mut config, mode, &theme)?; configure_hooks(&mut config, mode, &theme)?;
if Confirm::with_theme(&theme).with_prompt("Finish configuration?").default(true).interact()? { if Confirm::with_theme(&theme).with_prompt("Finish configuration?").default(true).interact()? {
break; break
} }
} }
@ -495,7 +498,7 @@ pub fn configure(name: Option<String>) -> Result<(), io::Error> {
let f = fs::File::create(&file)?; let f = fs::File::create(&file)?;
serde_yaml::to_writer(f, &config_file) serde_yaml::to_writer(f, &config_file)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse config file"))?; .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse config file"))?;
fs::set_permissions(file, fs::Permissions::from_mode(0o600))?; fs::set_permissions(file, fs::Permissions::from_mode(600))?;
println!(); println!();
println!("Use the following commands to control your VPN:"); println!("Use the following commands to control your VPN:");
println!(" start the VPN: sudo service vpncloud@{0} start", name); println!(" start the VPN: sudo service vpncloud@{0} start", name);

View File

@ -94,7 +94,7 @@ fn serve_proxy_connection(stream: TcpStream) -> Result<(), io::Error> {
} }
pub fn run_proxy(listen: &str) -> Result<(), io::Error> { pub fn run_proxy(listen: &str) -> Result<(), io::Error> {
let addr = parse_listen(listen, 8080); let addr = parse_listen(listen);
let server = TcpListener::bind(addr)?; let server = TcpListener::bind(addr)?;
info!("Listening on ws://{}", server.local_addr()?); info!("Listening on ws://{}", server.local_addr()?);
for stream in server.incoming() { for stream in server.incoming() {
@ -121,7 +121,7 @@ impl ProxyConnection {
unimplemented!(); unimplemented!();
/* /*
if let Message::Binary(data) = io_error!(self.socket.read_message(), "Failed to read from ws proxy: {}")? { if let Message::Binary(data) = io_error!(self.socket.read_message(), "Failed to read from ws proxy: {}")? {
return Ok(data); return Ok(data)
} }
*/ */
} }