mirror of https://github.com/dswd/vpncloud.git
Compare commits
12 Commits
675031a4ae
...
d118b96e03
Author | SHA1 | Date |
---|---|---|
Dennis Schwerdel | d118b96e03 | |
Dennis Schwerdel | c9a0cc85ab | |
Dennis Schwerdel | 450101d8ec | |
Dennis Schwerdel | 3da0d27eb7 | |
Dennis Schwerdel | 8e49311fef | |
Dennis Schwerdel | c63b2d1cd5 | |
Dennis Schwerdel | dd168139f0 | |
Dennis Schwerdel | caedec6fae | |
dswd | ac95f34402 | |
Dennis Schwerdel | 4950753548 | |
Dennis Schwerdel | c4ff0ed198 | |
dependabot[bot] | 6b88ac5733 |
|
@ -3,12 +3,27 @@ linker = "arm-linux-gnueabihf-gcc"
|
|||
objcopy = { path = "arm-linux-gnueabihf-objcopy" }
|
||||
strip = { path = "arm-linux-gnueabihf-strip" }
|
||||
|
||||
[target.armv7-unknown-linux-musleabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
||||
objcopy = { path = "arm-linux-gnueabihf-objcopy" }
|
||||
strip = { path = "arm-linux-gnueabihf-strip" }
|
||||
|
||||
[target.arm-unknown-linux-gnueabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
||||
objcopy = { path = "arm-linux-gnueabihf-objcopy" }
|
||||
strip = { path = "arm-linux-gnueabihf-strip" }
|
||||
|
||||
[target.arm-unknown-linux-musleabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
||||
objcopy = { path = "arm-linux-gnueabihf-objcopy" }
|
||||
strip = { path = "arm-linux-gnueabihf-strip" }
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
objcopy = { path = "aarch64-linux-gnu-objcopy" }
|
||||
strip = { path = "aarch64-linux-gnu-strip" }
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
objcopy = { path = "aarch64-linux-gnu-objcopy" }
|
||||
strip = { path = "aarch64-linux-gnu-strip" }
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
- name: Run builder
|
||||
uses: ./.github/actions/build-deb
|
||||
with:
|
||||
rust: '1.49.0'
|
||||
rust: '1.50.0'
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
|
@ -31,7 +31,7 @@ jobs:
|
|||
- name: Run builder
|
||||
uses: ./.github/actions/build-rpm
|
||||
with:
|
||||
rust: '1.49.0'
|
||||
rust: '1.50.0'
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
This project follows [semantic versioning](http://semver.org).
|
||||
|
||||
### UNRELEASED
|
||||
|
||||
- [added] Added interactive configuration wizard
|
||||
- [added] Support for (un-)installation
|
||||
- [added] Building static binaries
|
||||
- [changed] Restructured example config
|
||||
- [changed] Changed Rust version to 1.50.0
|
||||
- [changed] Updated dependencies
|
||||
|
||||
### v2.1.0 (2021-02-06)
|
||||
|
||||
- [added] Support for websocket proxy mode
|
||||
|
|
|
@ -143,6 +143,22 @@ dependencies = [
|
|||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"regex",
|
||||
"terminal_size",
|
||||
"unicode-width",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.5"
|
||||
|
@ -269,6 +285,18 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70f807b2943dc90f9747497d9d65d7e92472149be0b88bf4ce1201b4ac979c26"
|
||||
dependencies = [
|
||||
"console",
|
||||
"lazy_static",
|
||||
"tempfile",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
|
@ -296,6 +324,12 @@ version = "1.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -587,14 +621,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -690,9 +724,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -721,9 +755,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
|
||||
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
@ -764,15 +798,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
|
||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@ -913,9 +941,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdd2af560da3c1fdc02cb80965289254fc35dff869810061e2d8290ee48848ae"
|
||||
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"linked-hash-map",
|
||||
|
@ -925,9 +953,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4b312c3731e3fe78a185e6b9b911a7aa715b8e31cce117975219aab2acf285d"
|
||||
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
|
@ -1072,11 +1100,21 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall 0.2.4",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
|
@ -1280,6 +1318,7 @@ dependencies = [
|
|||
"byteorder",
|
||||
"criterion",
|
||||
"daemonize",
|
||||
"dialoguer",
|
||||
"fnv",
|
||||
"iai",
|
||||
"igd",
|
||||
|
@ -1443,3 +1482,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
|||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86"
|
||||
|
|
|
@ -22,7 +22,6 @@ libc = "0.2"
|
|||
rand = "0.8"
|
||||
fnv = "1"
|
||||
yaml-rust = "0.4"
|
||||
igd = { version = "0.12", optional = true }
|
||||
daemonize = "0.4"
|
||||
ring = "0.16"
|
||||
privdrop = "0.5"
|
||||
|
@ -30,8 +29,10 @@ byteorder = "1.4"
|
|||
thiserror = "1.0"
|
||||
parking_lot = "*"
|
||||
smallvec = "1.6"
|
||||
dialoguer = { version = "0.7", optional = true }
|
||||
tungstenite = { version = "0.13", optional = true, default-features = false }
|
||||
url = { version = "2.2", optional = true }
|
||||
igd = { version = "0.12", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
@ -39,9 +40,11 @@ criterion = { version = "0.3", features = ["html_reports"] }
|
|||
iai = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["nat", "websocket"]
|
||||
default = ["nat", "websocket", "wizard"]
|
||||
nat = ["igd"]
|
||||
websocket = ["tungstenite", "url"]
|
||||
wizard = ["dialoguer"]
|
||||
installer = []
|
||||
|
||||
[[bench]]
|
||||
name = "criterion"
|
||||
|
|
|
@ -11,6 +11,7 @@ RUN apt-get update \
|
|||
libc6-dev-i386 \
|
||||
gcc-5-multilib \
|
||||
asciidoctor \
|
||||
musl musl-dev musl-tools \
|
||||
&& rm -rf /var/cache/dpkg
|
||||
|
||||
RUN ln -s asm-generic/ /usr/include/asm
|
||||
|
@ -19,7 +20,7 @@ RUN useradd -ms /bin/bash user
|
|||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
ENV RUST=1.49.0
|
||||
ENV RUST=1.50.0
|
||||
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${RUST}
|
||||
|
||||
|
@ -27,12 +28,18 @@ ENV PATH=/home/user/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
|||
|
||||
RUN rustup target add i686-unknown-linux-gnu \
|
||||
&& rustup target add armv7-unknown-linux-gnueabihf \
|
||||
&& rustup target add aarch64-unknown-linux-gnu
|
||||
&& rustup target add aarch64-unknown-linux-gnu \
|
||||
&& rustup target add x86_64-unknown-linux-musl \
|
||||
&& rustup target add i686-unknown-linux-musl \
|
||||
&& rustup target add armv7-unknown-linux-musleabihf \
|
||||
&& rustup target add aarch64-unknown-linux-musl
|
||||
|
||||
RUN cargo install cargo-deb \
|
||||
&& rm -rf /home/user/.cargo/{git,tmp,registry}
|
||||
|
||||
ENV UPX_VER=3.96
|
||||
RUN curl https://github.com/upx/upx/releases/download/v${UPX_VER}/upx-${UPX_VER}-amd64_linux.tar.xz -Lf | tar -xJ --strip-components=1 -C /home/user/.cargo/bin
|
||||
|
||||
VOLUME /home/user/.cargo/tmp
|
||||
VOLUME /home/user/.cargo/git
|
||||
VOLUME /home/user/.cargo/registry
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ RUN useradd -ms /bin/bash user
|
|||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
ENV RUST=1.49.0
|
||||
ENV RUST=1.50.0
|
||||
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${RUST}
|
||||
|
||||
|
|
|
@ -32,24 +32,44 @@ mkdir -p ../dist
|
|||
docker build --rm -f=Dockerfile-deb -t vpncloud-builder-deb .
|
||||
|
||||
# x86_64 deb
|
||||
docker_cmd deb 'cd code && cargo deb'
|
||||
cp $CACHE/deb/target/debian/vpncloud_${DEB_VERSION}_amd64.deb ../dist/vpncloud_${DEB_VERSION}_amd64.deb
|
||||
if ! [ -f ../dist/vpncloud_${DEB_VERSION}_amd64.deb ]; then
|
||||
docker_cmd deb 'cd code && cargo deb'
|
||||
cp $CACHE/deb/target/debian/vpncloud_${DEB_VERSION}_amd64.deb ../dist/vpncloud_${DEB_VERSION}_amd64.deb
|
||||
fi
|
||||
|
||||
# i386 deb
|
||||
docker_cmd deb 'cd code && cargo deb --target i686-unknown-linux-gnu'
|
||||
cp $CACHE/deb/target/i686-unknown-linux-gnu/debian/vpncloud_${DEB_VERSION}_i386.deb ../dist/vpncloud_${DEB_VERSION}_i386.deb
|
||||
build_deb() {
|
||||
ARCH=$1
|
||||
TARGET=$2
|
||||
if ! [ -f ../dist/vpncloud_${DEB_VERSION}_${ARCH}.deb ]; then
|
||||
docker_cmd deb "cd code && cargo deb --target ${TARGET}"
|
||||
cp $CACHE/deb/target/${TARGET}/debian/vpncloud_${DEB_VERSION}_${ARCH}.deb ../dist/vpncloud_${DEB_VERSION}_${ARCH}.deb
|
||||
fi
|
||||
}
|
||||
|
||||
# arm7hf deb
|
||||
docker_cmd deb 'cd code && cargo deb --target armv7-unknown-linux-gnueabihf'
|
||||
cp $CACHE/deb/target/armv7-unknown-linux-gnueabihf/debian/vpncloud_${DEB_VERSION}_armhf.deb ../dist/vpncloud_${DEB_VERSION}_armhf.deb
|
||||
build_deb i386 i686-unknown-linux-gnu
|
||||
build_deb armhf armv7-unknown-linux-gnueabihf
|
||||
build_deb arm64 aarch64-unknown-linux-gnu
|
||||
|
||||
# aarch64 deb
|
||||
docker_cmd deb 'cd code && cargo deb --target aarch64-unknown-linux-gnu'
|
||||
cp $CACHE/deb/target/aarch64-unknown-linux-gnu/debian/vpncloud_${DEB_VERSION}_arm64.deb ../dist/vpncloud_${DEB_VERSION}_arm64.deb
|
||||
|
||||
build_static() {
|
||||
ARCH=$1
|
||||
TARGET=$2
|
||||
if ! [ -f ../dist/vpncloud_${VERSION}_static_${ARCH} ]; then
|
||||
docker_cmd deb "cd code && cargo build --release --features installer --target ${TARGET} && upx --lzma target/${TARGET}/release/vpncloud"
|
||||
cp $CACHE/deb/target/${TARGET}/release/vpncloud ../dist/vpncloud_${VERSION}_static_${ARCH}
|
||||
fi
|
||||
}
|
||||
|
||||
build_static amd64 x86_64-unknown-linux-musl
|
||||
build_static i386 i686-unknown-linux-gnu
|
||||
build_static armhf armv7-unknown-linux-musleabihf
|
||||
#build_static arm64 aarch64-unknown-linux-musl # fails for unknown reason
|
||||
|
||||
|
||||
docker build --rm -f=Dockerfile-rpm -t vpncloud-builder-rpm .
|
||||
|
||||
# x86_64 rpm
|
||||
docker_cmd rpm 'cd code && cargo rpm build'
|
||||
cp $CACHE/rpm/target/release/rpmbuild/RPMS/x86_64/vpncloud-${RPM_VERSION}.x86_64.rpm ../dist/vpncloud_${RPM_VERSION}.x86_64.rpm
|
||||
if ! [ -f ../dist/vpncloud_${RPM_VERSION}.x86_64.rpm ]; then
|
||||
# x86_64 rpm
|
||||
docker_cmd rpm 'cd code && cargo rpm build'
|
||||
cp $CACHE/rpm/target/release/rpmbuild/RPMS/x86_64/vpncloud-${RPM_VERSION}.x86_64.rpm ../dist/vpncloud_${RPM_VERSION}.x86_64.rpm
|
||||
fi
|
|
@ -0,0 +1,9 @@
|
|||
FROM ubuntu
|
||||
|
||||
RUN apt-get update && apt-get install -y asciinema
|
||||
RUN mkdir /root/.asciinema
|
||||
RUN mkdir /etc/vpncloud
|
||||
|
||||
WORKDIR /data
|
||||
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
|
|
@ -0,0 +1,3 @@
|
|||
[record]
|
||||
command = /usr/bin/bash -l
|
||||
idle_time_limit = 2.5
|
|
@ -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 "$@"
|
|
@ -303,6 +303,46 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_config_file(self) -> ConfigFile {
|
||||
ConfigFile {
|
||||
auto_claim: Some(self.auto_claim),
|
||||
claims: Some(self.claims),
|
||||
beacon: Some(ConfigFileBeacon {
|
||||
store: self.beacon_store,
|
||||
load: self.beacon_load,
|
||||
interval: Some(self.beacon_interval),
|
||||
password: self.beacon_password
|
||||
}),
|
||||
device: Some(ConfigFileDevice {
|
||||
name: Some(self.device_name),
|
||||
path: self.device_path,
|
||||
type_: Some(self.device_type),
|
||||
fix_rp_filter: Some(self.fix_rp_filter)
|
||||
}),
|
||||
crypto: self.crypto,
|
||||
group: self.group,
|
||||
user: self.user,
|
||||
ifup: self.ifup,
|
||||
ifdown: self.ifdown,
|
||||
ip: self.ip,
|
||||
keepalive: self.keepalive,
|
||||
listen: Some(self.listen),
|
||||
mode: Some(self.mode),
|
||||
peer_timeout: Some(self.peer_timeout),
|
||||
peers: Some(self.peers),
|
||||
pid_file: self.pid_file,
|
||||
port_forwarding: Some(self.port_forwarding),
|
||||
stats_file: self.stats_file,
|
||||
statsd: Some(ConfigFileStatsd {
|
||||
server: self.statsd_server,
|
||||
prefix: self.statsd_prefix
|
||||
}),
|
||||
switch_timeout: Some(self.switch_timeout),
|
||||
hook: self.hook,
|
||||
hooks: self.hooks
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_keepalive(&self) -> Duration {
|
||||
match self.keepalive {
|
||||
Some(dur) => dur,
|
||||
|
@ -310,6 +350,34 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_learning(&self) -> bool {
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
match self.device_type {
|
||||
Type::Tap => true,
|
||||
Type::Tun => false
|
||||
}
|
||||
}
|
||||
Mode::Router => false,
|
||||
Mode::Switch => true,
|
||||
Mode::Hub => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_broadcasting(&self) -> bool {
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
match self.device_type {
|
||||
Type::Tap => true,
|
||||
Type::Tun => false
|
||||
}
|
||||
}
|
||||
Mode::Router => false,
|
||||
Mode::Switch => true,
|
||||
Mode::Hub => true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_hook(
|
||||
&self, event: &'static str, envs: impl IntoIterator<Item = (&'static str, impl AsRef<OsStr>)>, detach: bool
|
||||
) {
|
||||
|
@ -525,6 +593,22 @@ pub enum Command {
|
|||
/// Shell to create completions for
|
||||
#[structopt(long, default_value="bash")]
|
||||
shell: Shell
|
||||
},
|
||||
|
||||
/// Edit the config of a network
|
||||
#[cfg(feature = "wizard")]
|
||||
Config {
|
||||
/// Name of the network
|
||||
#[structopt(short, long)]
|
||||
name: Option<String>
|
||||
},
|
||||
|
||||
/// Install required utility files
|
||||
#[cfg(feature = "installer")]
|
||||
Install {
|
||||
/// Remove installed files again
|
||||
#[structopt(long)]
|
||||
uninstall: bool
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{core::test_speed, rotate::RotationState};
|
||||
pub use super::{
|
||||
core::{CryptoCore, EXTRA_LEN, TAG_LEN},
|
||||
init::{is_init_message, INIT_MESSAGE_FIRST_BYTE, InitState, InitResult}
|
||||
init::{is_init_message, InitResult, InitState, INIT_MESSAGE_FIRST_BYTE}
|
||||
};
|
||||
use crate::{
|
||||
error::Error,
|
||||
|
@ -69,6 +69,27 @@ pub struct Crypto {
|
|||
}
|
||||
|
||||
impl Crypto {
|
||||
pub fn parse_algorithms(algos: &[String]) -> Result<(bool, Vec<&'static aead::Algorithm>), Error> {
|
||||
let algorithms = algos.iter().map(|a| a as &str).collect::<Vec<_>>();
|
||||
let allowed = if algorithms.is_empty() { &DEFAULT_ALGORITHMS } else { &algorithms as &[&str] };
|
||||
let mut algos = vec![];
|
||||
let mut unencrypted = false;
|
||||
for name in allowed {
|
||||
let algo = match &name.to_uppercase() as &str {
|
||||
"UNENCRYPTED" | "NONE" | "PLAIN" => {
|
||||
unencrypted = true;
|
||||
continue
|
||||
}
|
||||
"AES128" | "AES128_GCM" | "AES_128" | "AES_128_GCM" => &aead::AES_128_GCM,
|
||||
"AES256" | "AES256_GCM" | "AES_256" | "AES_256_GCM" => &aead::AES_256_GCM,
|
||||
"CHACHA" | "CHACHA20" | "CHACHA20_POLY1305" => &aead::CHACHA20_POLY1305,
|
||||
_ => return Err(Error::InvalidConfig("Unknown crypto method"))
|
||||
};
|
||||
algos.push(algo)
|
||||
}
|
||||
Ok((unencrypted, algos))
|
||||
}
|
||||
|
||||
pub fn new(node_id: NodeId, config: &Config) -> Result<Self, Error> {
|
||||
let key_pair = if let Some(priv_key) = &config.private_key {
|
||||
if let Some(pub_key) = &config.public_key {
|
||||
|
@ -91,26 +112,17 @@ impl Crypto {
|
|||
key.clone_from_slice(key_pair.public_key().as_ref());
|
||||
trusted_keys.push(key);
|
||||
}
|
||||
let mut algos = Algorithms { algorithm_speeds: smallvec![], allow_unencrypted: false };
|
||||
let algorithms = config.algorithms.iter().map(|a| a as &str).collect::<Vec<_>>();
|
||||
let allowed = if algorithms.is_empty() { &DEFAULT_ALGORITHMS } else { &algorithms as &[&str] };
|
||||
let (unencrypted, allowed_algos) = Self::parse_algorithms(&config.algorithms)?;
|
||||
if unencrypted {
|
||||
warn!("Crypto settings allow unencrypted connections")
|
||||
}
|
||||
let mut algos = Algorithms { algorithm_speeds: smallvec![], allow_unencrypted: unencrypted };
|
||||
let duration = Duration::from_secs_f32(SPEED_TEST_TIME);
|
||||
let mut speeds = Vec::new();
|
||||
for name in allowed {
|
||||
let algo = match &name.to_uppercase() as &str {
|
||||
"UNENCRYPTED" | "NONE" | "PLAIN" => {
|
||||
algos.allow_unencrypted = true;
|
||||
warn!("Crypto settings allow unencrypted connections");
|
||||
continue
|
||||
}
|
||||
"AES128" | "AES128_GCM" | "AES_128" | "AES_128_GCM" => &aead::AES_128_GCM,
|
||||
"AES256" | "AES256_GCM" | "AES_256" | "AES_256_GCM" => &aead::AES_256_GCM,
|
||||
"CHACHA" | "CHACHA20" | "CHACHA20_POLY1305" => &aead::CHACHA20_POLY1305,
|
||||
_ => return Err(Error::InvalidConfig("Unknown crypto method"))
|
||||
};
|
||||
for algo in allowed_algos {
|
||||
let speed = test_speed(algo, &duration);
|
||||
algos.algorithm_speeds.push((algo, speed as f32));
|
||||
speeds.push((name, speed as f32));
|
||||
speeds.push((format!("{:?}", algo), speed as f32));
|
||||
}
|
||||
if !speeds.is_empty() {
|
||||
info!(
|
||||
|
@ -170,6 +182,14 @@ impl Crypto {
|
|||
Ok(keypair)
|
||||
}
|
||||
|
||||
pub fn public_key_from_private_key(privkey: &str) -> Result<String, Error> {
|
||||
let privkey = from_base62(privkey).map_err(|_| Error::InvalidConfig("Failed to parse private key"))?;
|
||||
let keypair = Ed25519KeyPair::from_seed_unchecked(&privkey)
|
||||
.map_err(|_| Error::InvalidConfig("Key rejected by crypto library"))?;
|
||||
let pubkey = to_base62(keypair.public_key().as_ref());
|
||||
Ok(pubkey)
|
||||
}
|
||||
|
||||
fn parse_public_key(pubkey: &str) -> Result<Ed25519PublicKey, Error> {
|
||||
let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?;
|
||||
if pubkey.len() != ED25519_PUBLIC_KEY_LEN {
|
||||
|
@ -283,7 +303,7 @@ impl PeerCrypto {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn every_second(&mut self, out: &mut MsgBuffer) -> MessageResult {
|
||||
pub fn every_second(&mut self, out: &mut MsgBuffer) {
|
||||
out.clear();
|
||||
if let PeerCrypto::Encrypted { core, rotation, rotate_counter, algorithm, .. } = self {
|
||||
core.every_second();
|
||||
|
@ -297,11 +317,9 @@ impl PeerCrypto {
|
|||
if !out.is_empty() {
|
||||
out.prepend_byte(MESSAGE_TYPE_ROTATION);
|
||||
self.encrypt_message(out);
|
||||
return MessageResult::Reply
|
||||
}
|
||||
}
|
||||
}
|
||||
MessageResult::None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,9 +363,9 @@ mod tests {
|
|||
assert_eq!(res, InitResult::Success { peer_payload: vec![], is_initiator: true });
|
||||
assert!(msg.is_empty());
|
||||
|
||||
let node1 = node1.finish(&mut msg);
|
||||
let mut node1 = node1.finish(&mut msg);
|
||||
assert!(msg.is_empty());
|
||||
let node2 = node2.finish(&mut msg);
|
||||
let mut node2 = node2.finish(&mut msg);
|
||||
assert!(msg.is_empty());
|
||||
|
||||
debug!("Node1 <- Node2");
|
||||
|
@ -365,22 +383,16 @@ mod tests {
|
|||
let res = node2.handle_message(&mut buffer).unwrap();
|
||||
assert_eq!(res, MessageResult::Message(1));
|
||||
|
||||
match node1.every_second(&mut msg) {
|
||||
MessageResult::None => (),
|
||||
MessageResult::Reply => {
|
||||
node1.every_second(&mut msg);
|
||||
if !msg.is_empty() {
|
||||
let res = node2.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::None);
|
||||
}
|
||||
other => assert_eq!(other, MessageResult::None)
|
||||
}
|
||||
match node2.every_second(&mut msg) {
|
||||
MessageResult::None => (),
|
||||
MessageResult::Reply => {
|
||||
node2.every_second(&mut msg);
|
||||
if !msg.is_empty() {
|
||||
let res = node1.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::None);
|
||||
}
|
||||
other => assert_eq!(other, MessageResult::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,7 +258,7 @@ pub fn create_dummy_pair(algo: &'static aead::Algorithm) -> (CryptoCore, CryptoC
|
|||
pub fn test_speed(algo: &'static aead::Algorithm, max_time: &Duration) -> f64 {
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.set_length(1000);
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let (sender, receiver) = create_dummy_pair(algo);
|
||||
let mut iterations = 0;
|
||||
let start = Instant::now();
|
||||
while (Instant::now() - start).as_nanos() < max_time.as_nanos() {
|
||||
|
@ -290,7 +290,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_encrypt_decrypt(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let (sender, receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
|
@ -318,7 +318,7 @@ mod tests {
|
|||
|
||||
|
||||
fn test_tampering(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let (sender, receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
|
@ -358,7 +358,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_nonce_pinning(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let (sender, receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
|
@ -399,7 +399,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn test_key_rotation(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let (sender, receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cmp,
|
||||
collections::VecDeque,
|
||||
|
@ -12,7 +13,8 @@ use std::{
|
|||
net::{Ipv4Addr, UdpSocket},
|
||||
os::unix::io::{AsRawFd, RawFd},
|
||||
str,
|
||||
str::FromStr
|
||||
str::FromStr,
|
||||
sync::Arc
|
||||
};
|
||||
|
||||
use crate::{crypto, error::Error, util::MsgBuffer};
|
||||
|
@ -75,7 +77,7 @@ impl FromStr for Type {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Device: AsRawFd {
|
||||
pub trait Device: AsRawFd + Clone + Send + 'static {
|
||||
/// Returns the type of this device
|
||||
fn get_type(&self) -> Type;
|
||||
|
||||
|
@ -118,6 +120,16 @@ pub struct TunTapDevice {
|
|||
}
|
||||
|
||||
|
||||
impl Clone for TunTapDevice {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
fd: try_fail!(self.fd.try_clone(), "Failed to clone device: {}"),
|
||||
ifname: self.ifname.clone(),
|
||||
type_: self.type_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TunTapDevice {
|
||||
/// Creates a new tun/tap device
|
||||
///
|
||||
|
@ -300,9 +312,10 @@ impl AsRawFd for TunTapDevice {
|
|||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MockDevice {
|
||||
inbound: VecDeque<Vec<u8>>,
|
||||
outbound: VecDeque<Vec<u8>>
|
||||
inbound: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
||||
outbound: Arc<Mutex<VecDeque<Vec<u8>>>>
|
||||
}
|
||||
|
||||
impl MockDevice {
|
||||
|
@ -311,15 +324,15 @@ impl MockDevice {
|
|||
}
|
||||
|
||||
pub fn put_inbound(&mut self, data: Vec<u8>) {
|
||||
self.inbound.push_back(data)
|
||||
self.inbound.lock().push_back(data)
|
||||
}
|
||||
|
||||
pub fn pop_outbound(&mut self) -> Option<Vec<u8>> {
|
||||
self.outbound.pop_front()
|
||||
self.outbound.lock().pop_front()
|
||||
}
|
||||
|
||||
pub fn has_inbound(&self) -> bool {
|
||||
!self.inbound.is_empty()
|
||||
!self.inbound.lock().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,7 +346,7 @@ impl Device for MockDevice {
|
|||
}
|
||||
|
||||
fn read(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
if let Some(data) = self.inbound.pop_front() {
|
||||
if let Some(data) = self.inbound.lock().pop_front() {
|
||||
buffer.clear();
|
||||
buffer.set_length(data.len());
|
||||
buffer.message_mut().copy_from_slice(&data);
|
||||
|
@ -344,7 +357,7 @@ impl Device for MockDevice {
|
|||
}
|
||||
|
||||
fn write(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
self.outbound.push_back(buffer.message().into());
|
||||
self.outbound.lock().push_back(buffer.message().into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -355,7 +368,10 @@ impl Device for MockDevice {
|
|||
|
||||
impl Default for MockDevice {
|
||||
fn default() -> Self {
|
||||
Self { outbound: VecDeque::with_capacity(10), inbound: VecDeque::with_capacity(10) }
|
||||
Self {
|
||||
outbound: Arc::new(Mutex::new(VecDeque::with_capacity(10))),
|
||||
inbound: Arc::new(Mutex::new(VecDeque::with_capacity(10)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ use crate::{
|
|||
messages::MESSAGE_TYPE_DATA,
|
||||
net::Socket,
|
||||
util::{MsgBuffer, Time, TimeSource},
|
||||
Protocol
|
||||
Protocol,
|
||||
config::Config
|
||||
};
|
||||
use std::{marker::PhantomData, net::SocketAddr};
|
||||
|
||||
|
@ -28,6 +29,20 @@ pub struct DeviceThread<S: Socket, D: Device, P: Protocol, TS: TimeSource> {
|
|||
}
|
||||
|
||||
impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> DeviceThread<S, D, P, TS> {
|
||||
pub fn new(config: Config, device: D, socket: S, traffic: SharedTraffic, peer_crypto: SharedPeerCrypto, table: SharedTable<TS>) -> Self {
|
||||
Self {
|
||||
_dummy_ts: PhantomData,
|
||||
_dummy_p: PhantomData,
|
||||
broadcast: config.is_broadcasting(),
|
||||
socket,
|
||||
device,
|
||||
next_housekeep: TS::now(),
|
||||
traffic,
|
||||
peer_crypto,
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send_to(&mut self, addr: SocketAddr, msg: &mut MsgBuffer) -> Result<(), Error> {
|
||||
debug!("Sending msg with {} bytes to {}", msg.len(), addr);
|
||||
|
@ -51,6 +66,8 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> DeviceThread<S, D, P, TS
|
|||
fn broadcast_msg(&mut self, type_: u8, msg: &mut MsgBuffer) -> Result<(), Error> {
|
||||
debug!("Broadcasting message type {}, {:?} bytes to {} peers", type_, msg.len(), self.peer_crypto.count());
|
||||
let mut msg_data = MsgBuffer::new(100);
|
||||
let traffic = &mut self.traffic;
|
||||
let socket = &mut self.socket;
|
||||
self.peer_crypto.for_each(|addr, crypto| {
|
||||
msg_data.set_start(msg.get_start());
|
||||
msg_data.set_length(msg.len());
|
||||
|
@ -59,8 +76,8 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> DeviceThread<S, D, P, TS
|
|||
if let Some(crypto) = crypto {
|
||||
crypto.encrypt(&mut msg_data);
|
||||
}
|
||||
self.traffic.count_out_traffic(addr, msg_data.len());
|
||||
match self.socket.send(msg_data.message(), addr) {
|
||||
traffic.count_out_traffic(addr, msg_data.len());
|
||||
match socket.send(msg_data.message(), addr) {
|
||||
Ok(written) if written == msg_data.len() => Ok(()),
|
||||
Ok(_) => Err(Error::Socket("Sent out truncated packet")),
|
||||
Err(e) => Err(Error::SocketIo("IOError when sending", e))
|
||||
|
@ -102,15 +119,16 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> DeviceThread<S, D, P, TS
|
|||
let mut buffer = MsgBuffer::new(SPACE_BEFORE);
|
||||
loop {
|
||||
try_fail!(self.device.read(&mut buffer), "Failed to read from device: {}");
|
||||
//TODO: set and handle timeout
|
||||
if let Err(e) = self.forward_packet(&mut buffer) {
|
||||
error!("{}", e);
|
||||
}
|
||||
let now = TS::now();
|
||||
if self.next_housekeep < TS::now() {
|
||||
if self.next_housekeep < now {
|
||||
if let Err(e) = self.housekeep() {
|
||||
error!("{}", e)
|
||||
}
|
||||
self.next_housekeep = TS::now() + 1
|
||||
self.next_housekeep = now + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,21 +1,31 @@
|
|||
use crate::error::Error;
|
||||
use crate::{
|
||||
config::Config,
|
||||
crypto::CryptoCore,
|
||||
engine::{Hash, PeerData, TimeSource},
|
||||
messages::NodeInfo,
|
||||
engine::{Hash, TimeSource},
|
||||
error::Error,
|
||||
table::ClaimTable,
|
||||
traffic::TrafficStats,
|
||||
types::{Address, NodeId, RangeList},
|
||||
util::MsgBuffer
|
||||
traffic::{TrafficEntry, TrafficStats},
|
||||
types::{Address, RangeList},
|
||||
util::{Duration, MsgBuffer}
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
net::SocketAddr,
|
||||
sync::Arc
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedPeerCrypto {
|
||||
peers: Arc<Mutex<HashMap<SocketAddr, Option<Arc<CryptoCore>>, Hash>>>
|
||||
}
|
||||
|
||||
impl SharedPeerCrypto {
|
||||
pub fn new() -> Self {
|
||||
SharedPeerCrypto { peers: Arc::new(Mutex::new(HashMap::default())) }
|
||||
}
|
||||
|
||||
pub fn sync(&mut self) {
|
||||
// TODO sync if needed
|
||||
}
|
||||
|
@ -25,11 +35,16 @@ impl SharedPeerCrypto {
|
|||
match peers.get_mut(&peer) {
|
||||
None => Err(Error::InvalidCryptoState("No crypto found for peer")),
|
||||
Some(None) => Ok(()),
|
||||
Some(Some(crypto)) => Ok(crypto.encrypt(data))
|
||||
Some(Some(crypto)) => {
|
||||
crypto.encrypt(data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each(&mut self, mut callback: impl FnMut(SocketAddr, Option<Arc<CryptoCore>>) -> Result<(), Error>) -> Result<(), Error> {
|
||||
pub fn for_each(
|
||||
&mut self, mut callback: impl FnMut(SocketAddr, Option<Arc<CryptoCore>>) -> Result<(), Error>
|
||||
) -> Result<(), Error> {
|
||||
let mut peers = self.peers.lock();
|
||||
for (k, v) in peers.iter_mut() {
|
||||
callback(*k, v.clone())?
|
||||
|
@ -43,11 +58,16 @@ impl SharedPeerCrypto {
|
|||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedTraffic {
|
||||
traffic: Arc<Mutex<TrafficStats>>
|
||||
}
|
||||
|
||||
impl SharedTraffic {
|
||||
pub fn new() -> Self {
|
||||
Self { traffic: Arc::new(Mutex::new(Default::default())) }
|
||||
}
|
||||
|
||||
pub fn sync(&mut self) {
|
||||
// TODO sync if needed
|
||||
}
|
||||
|
@ -75,31 +95,73 @@ impl SharedTraffic {
|
|||
pub fn count_invalid_protocol(&self, bytes: usize) {
|
||||
self.traffic.lock().count_invalid_protocol(bytes);
|
||||
}
|
||||
|
||||
pub fn period(&mut self, cleanup_idle: Option<usize>) {
|
||||
self.traffic.lock().period(cleanup_idle)
|
||||
}
|
||||
|
||||
pub fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
|
||||
self.traffic.lock().write_out(out)
|
||||
}
|
||||
|
||||
pub fn total_peer_traffic(&self) -> TrafficEntry {
|
||||
self.traffic.lock().total_peer_traffic()
|
||||
}
|
||||
|
||||
pub fn total_payload_traffic(&self) -> TrafficEntry {
|
||||
self.traffic.lock().total_payload_traffic()
|
||||
}
|
||||
|
||||
pub fn dropped(&self) -> TrafficEntry {
|
||||
self.traffic.lock().dropped.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedTable<TS: TimeSource> {
|
||||
table: Arc<Mutex<ClaimTable<TS>>>
|
||||
}
|
||||
|
||||
impl<TS: TimeSource> SharedTable<TS> {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
let table = ClaimTable::new(config.switch_timeout as Duration, config.peer_timeout as Duration);
|
||||
SharedTable { table: Arc::new(Mutex::new(table)) }
|
||||
}
|
||||
|
||||
pub fn sync(&mut self) {
|
||||
// TODO sync if needed
|
||||
}
|
||||
|
||||
pub fn lookup(&self, addr: Address) -> Option<SocketAddr> {
|
||||
pub fn lookup(&mut self, addr: Address) -> Option<SocketAddr> {
|
||||
self.table.lock().lookup(addr)
|
||||
}
|
||||
|
||||
pub fn set_claims(&self, peer: SocketAddr, claims: RangeList) {
|
||||
pub fn set_claims(&mut self, peer: SocketAddr, claims: RangeList) {
|
||||
self.table.lock().set_claims(peer, claims)
|
||||
}
|
||||
|
||||
pub fn remove_claims(&self, peer: SocketAddr) {
|
||||
pub fn remove_claims(&mut self, peer: SocketAddr) {
|
||||
self.table.lock().remove_claims(peer)
|
||||
}
|
||||
|
||||
pub fn cache(&self, addr: Address, peer: SocketAddr) {
|
||||
pub fn cache(&mut self, addr: Address, peer: SocketAddr) {
|
||||
self.table.lock().cache(addr, peer)
|
||||
}
|
||||
|
||||
pub fn housekeep(&mut self) {
|
||||
self.table.lock().housekeep()
|
||||
}
|
||||
|
||||
pub fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
|
||||
self.table.lock().write_out(out)
|
||||
}
|
||||
|
||||
pub fn cache_len(&self) -> usize {
|
||||
self.table.lock().cache_len()
|
||||
}
|
||||
|
||||
pub fn claim_len(&self) -> usize {
|
||||
self.table.lock().claim_len()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,26 +4,53 @@ use super::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
config::DEFAULT_PEER_TIMEOUT,
|
||||
crypto::{is_init_message, MessageResult, PeerCrypto, InitState, InitResult},
|
||||
beacon::BeaconSerializer,
|
||||
config::{DEFAULT_PEER_TIMEOUT, DEFAULT_PORT},
|
||||
crypto::{is_init_message, InitResult, InitState, MessageResult},
|
||||
device::Type,
|
||||
engine::{addr_nice, resolve, Hash, PeerData},
|
||||
error::Error,
|
||||
messages::{AddrList, NodeInfo, PeerInfo},
|
||||
messages::{
|
||||
AddrList, NodeInfo, PeerInfo, MESSAGE_TYPE_CLOSE, MESSAGE_TYPE_DATA, MESSAGE_TYPE_KEEPALIVE,
|
||||
MESSAGE_TYPE_NODE_INFO
|
||||
},
|
||||
net::{mapped_addr, Socket},
|
||||
types::{NodeId, RangeList},
|
||||
util::{MsgBuffer, Time, TimeSource},
|
||||
port_forwarding::PortForwarding,
|
||||
types::{Address, NodeId, Range, RangeList},
|
||||
util::{MsgBuffer, StatsdMsg, Time, TimeSource},
|
||||
Config, Crypto, Device, Protocol
|
||||
};
|
||||
use rand::{seq::SliceRandom};
|
||||
use rand::{random, seq::SliceRandom, thread_rng};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
io::Cursor,
|
||||
fs::File,
|
||||
io,
|
||||
io::{Cursor, Seek, SeekFrom, Write},
|
||||
marker::PhantomData,
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
str::FromStr
|
||||
};
|
||||
|
||||
|
||||
const MAX_RECONNECT_INTERVAL: u16 = 3600;
|
||||
const RESOLVE_INTERVAL: Time = 300;
|
||||
const OWN_ADDRESS_RESET_INTERVAL: Time = 300;
|
||||
pub const STATS_INTERVAL: Time = 60;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReconnectEntry {
|
||||
address: Option<(String, Time)>,
|
||||
resolved: AddrList,
|
||||
tries: u16,
|
||||
timeout: u16,
|
||||
next: Time,
|
||||
final_timeout: Option<Time>
|
||||
}
|
||||
|
||||
pub struct SocketThread<S: Socket, D: Device, P: Protocol, TS: TimeSource> {
|
||||
// Read-only fields
|
||||
node_id: NodeId,
|
||||
|
@ -31,6 +58,7 @@ pub struct SocketThread<S: Socket, D: Device, P: Protocol, TS: TimeSource> {
|
|||
config: Config,
|
||||
peer_timeout_publish: u16,
|
||||
learning: bool,
|
||||
update_freq: u16,
|
||||
_dummy_ts: PhantomData<TS>,
|
||||
_dummy_p: PhantomData<P>,
|
||||
// Socket-only fields
|
||||
|
@ -38,15 +66,83 @@ pub struct SocketThread<S: Socket, D: Device, P: Protocol, TS: TimeSource> {
|
|||
device: D,
|
||||
next_housekeep: Time,
|
||||
own_addresses: AddrList,
|
||||
next_own_address_reset: Time,
|
||||
pending_inits: HashMap<SocketAddr, InitState<NodeInfo>, Hash>,
|
||||
crypto: Crypto,
|
||||
peers: HashMap<SocketAddr, PeerData, Hash>,
|
||||
next_peers: Time,
|
||||
next_stats_out: Time,
|
||||
next_beacon: Time,
|
||||
beacon_serializer: BeaconSerializer<TS>,
|
||||
stats_file: Option<File>,
|
||||
statsd_server: Option<String>,
|
||||
reconnect_peers: SmallVec<[ReconnectEntry; 3]>,
|
||||
// Shared fields
|
||||
peer_crypto: SharedPeerCrypto,
|
||||
traffic: SharedTraffic,
|
||||
table: SharedTable<TS>
|
||||
table: SharedTable<TS>,
|
||||
// Should not be here
|
||||
port_forwarding: Option<PortForwarding> // TODO: 3rd thread
|
||||
}
|
||||
|
||||
impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS> {
|
||||
pub fn new(
|
||||
config: Config, device: D, socket: S, traffic: SharedTraffic, peer_crypto: SharedPeerCrypto,
|
||||
table: SharedTable<TS>, port_forwarding: Option<PortForwarding>, stats_file: Option<File>
|
||||
) -> Self {
|
||||
let mut claims = SmallVec::with_capacity(config.claims.len());
|
||||
for s in &config.claims {
|
||||
claims.push(try_fail!(Range::from_str(s), "Invalid subnet format: {} ({})", s));
|
||||
}
|
||||
if device.get_type() == Type::Tun && config.auto_claim {
|
||||
match device.get_ip() {
|
||||
Ok(ip) => {
|
||||
let range = Range { base: Address::from_ipv4(ip), prefix_len: 32 };
|
||||
info!("Auto-claiming {} due to interface address", range);
|
||||
claims.push(range);
|
||||
}
|
||||
Err(Error::DeviceIo(_, e)) if e.kind() == io::ErrorKind::AddrNotAvailable => {
|
||||
info!("No address set on interface.")
|
||||
}
|
||||
Err(e) => error!("{}", e)
|
||||
}
|
||||
}
|
||||
let now = TS::now();
|
||||
let update_freq = config.get_keepalive() as u16;
|
||||
let node_id = random();
|
||||
let crypto = Crypto::new(node_id, &config.crypto).unwrap();
|
||||
let beacon_key = config.beacon_password.as_ref().map(|s| s.as_bytes()).unwrap_or(&[]);
|
||||
Self {
|
||||
_dummy_p: PhantomData,
|
||||
_dummy_ts: PhantomData,
|
||||
node_id,
|
||||
claims,
|
||||
device,
|
||||
socket,
|
||||
peer_crypto,
|
||||
traffic,
|
||||
table,
|
||||
learning: config.is_learning(),
|
||||
next_housekeep: now,
|
||||
next_beacon: now,
|
||||
next_peers: now,
|
||||
next_stats_out: now + STATS_INTERVAL,
|
||||
next_own_address_reset: now + OWN_ADDRESS_RESET_INTERVAL,
|
||||
pending_inits: HashMap::default(),
|
||||
reconnect_peers: SmallVec::new(),
|
||||
own_addresses: SmallVec::new(),
|
||||
peers: HashMap::default(),
|
||||
peer_timeout_publish: config.peer_timeout as u16,
|
||||
beacon_serializer: BeaconSerializer::new(beacon_key),
|
||||
port_forwarding,
|
||||
stats_file,
|
||||
update_freq,
|
||||
statsd_server: config.statsd_server.clone(),
|
||||
crypto: Crypto::new(node_id, &config.crypto).unwrap(),
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send_to(&mut self, addr: SocketAddr, msg: &MsgBuffer) -> Result<(), Error> {
|
||||
debug!("Sending msg with {} bytes to {}", msg.len(), addr);
|
||||
|
@ -58,6 +154,26 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn broadcast_msg(&mut self, type_: u8, msg: &mut MsgBuffer) -> Result<(), Error> {
|
||||
debug!("Broadcasting message type {}, {:?} bytes to {} peers", type_, msg.len(), self.peers.len());
|
||||
let mut msg_data = MsgBuffer::new(100);
|
||||
for (addr, peer) in &mut self.peers {
|
||||
msg_data.set_start(msg.get_start());
|
||||
msg_data.set_length(msg.len());
|
||||
msg_data.message_mut().clone_from_slice(msg.message());
|
||||
msg_data.prepend_byte(type_);
|
||||
peer.crypto.encrypt_message(&mut msg_data);
|
||||
self.traffic.count_out_traffic(*addr, msg_data.len());
|
||||
match self.socket.send(msg_data.message(), *addr) {
|
||||
Ok(written) if written == msg_data.len() => Ok(()),
|
||||
Ok(_) => Err(Error::Socket("Sent out truncated packet")),
|
||||
Err(e) => Err(Error::SocketIo("IOError when sending", e))
|
||||
}?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect_sock(&mut self, addr: SocketAddr) -> Result<(), Error> {
|
||||
let addr = mapped_addr(addr);
|
||||
if self.peers.contains_key(&addr)
|
||||
|
@ -72,7 +188,7 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
let mut msg = MsgBuffer::new(SPACE_BEFORE);
|
||||
init.send_ping(&mut msg);
|
||||
self.pending_inits.insert(addr, init);
|
||||
self.send_to(addr, &mut msg)
|
||||
self.send_to(addr, &msg)
|
||||
}
|
||||
|
||||
pub fn connect<Addr: ToSocketAddrs + fmt::Debug + Clone>(&mut self, addr: Addr) -> Result<(), Error> {
|
||||
|
@ -133,7 +249,7 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
info!("Added peer {}", addr_nice(addr));
|
||||
if let Some(init) = self.pending_inits.remove(&addr) {
|
||||
msg.clear();
|
||||
let crypto = init.finish(&mut msg);
|
||||
let crypto = init.finish(msg);
|
||||
self.peers.insert(addr, PeerData {
|
||||
addrs: info.addrs.clone(),
|
||||
crypto,
|
||||
|
@ -231,9 +347,7 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
fn handle_message(&mut self, src: SocketAddr, data: &mut MsgBuffer) -> Result<(), Error> {
|
||||
let src = mapped_addr(src);
|
||||
debug!("Received {} bytes from {}", data.len(), src);
|
||||
if let Some(result) = self.peers.get_mut(&src).map(|peer| {
|
||||
peer.crypto.handle_message(data)
|
||||
}) {
|
||||
if let Some(result) = self.peers.get_mut(&src).map(|peer| peer.crypto.handle_message(data)) {
|
||||
return self.process_message(src, result?, data)
|
||||
}
|
||||
let is_init = is_init_message(data.message());
|
||||
|
@ -251,10 +365,8 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
if !data.is_empty() {
|
||||
self.send_to(src, data)?
|
||||
}
|
||||
},
|
||||
InitResult::Success { peer_payload, is_initiator } => {
|
||||
self.add_new_peer(src, peer_payload, data)?
|
||||
}
|
||||
InitResult::Success { peer_payload, .. } => self.add_new_peer(src, peer_payload, data)?
|
||||
}
|
||||
return Ok(())
|
||||
}
|
||||
|
@ -266,40 +378,314 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
let mut init = self.crypto.peer_instance(self.create_node_info());
|
||||
let msg_result = init.handle_init(data);
|
||||
match msg_result {
|
||||
Ok(res) => {
|
||||
Ok(_) => {
|
||||
self.pending_inits.insert(src, init);
|
||||
self.send_to(src, data)
|
||||
}
|
||||
Err(err) => {
|
||||
self.traffic.count_invalid_protocol(data.len());
|
||||
return Err(err)
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn housekeep(&mut self) -> Result<(), Error> {
|
||||
// self.shared.sync();
|
||||
// * = can be in different thread, ** only with caching/sync
|
||||
//TODO: peers: timeout **
|
||||
//TODO: table: timeout **
|
||||
//TODO: rotate crypto keys
|
||||
//TODO: time out pending inits
|
||||
//TODO: extend port forwarding *
|
||||
//TODO: reset own address **
|
||||
//TODO: send peer lists **
|
||||
//TODO: reconnect to peers **
|
||||
//TODO: write to statsd **
|
||||
//TODO: write to stats file **
|
||||
//TODO: read beacon **
|
||||
//TODO: write beacon **
|
||||
// TODO: sync
|
||||
let now = TS::now();
|
||||
let mut buffer = MsgBuffer::new(SPACE_BEFORE);
|
||||
let mut del: SmallVec<[SocketAddr; 3]> = SmallVec::new();
|
||||
for (&addr, ref data) in &self.peers {
|
||||
if data.timeout < now {
|
||||
del.push(addr);
|
||||
}
|
||||
}
|
||||
for addr in del {
|
||||
info!("Forgot peer {} due to timeout", addr_nice(addr));
|
||||
self.peers.remove(&addr);
|
||||
self.table.remove_claims(addr);
|
||||
self.connect_sock(addr)?; // Try to reconnect
|
||||
}
|
||||
self.table.housekeep();
|
||||
self.crypto_housekeep()?;
|
||||
// Periodically extend the port-forwarding
|
||||
if let Some(ref mut pfw) = self.port_forwarding {
|
||||
pfw.check_extend();
|
||||
}
|
||||
let now = TS::now();
|
||||
// Periodically reset own peers
|
||||
if self.next_own_address_reset <= now {
|
||||
self.reset_own_addresses().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
|
||||
if self.next_peers <= now {
|
||||
debug!("Send peer list to all peers");
|
||||
let info = self.create_node_info();
|
||||
info.encode(&mut buffer);
|
||||
self.broadcast_msg(MESSAGE_TYPE_NODE_INFO, &mut buffer)?;
|
||||
// Reschedule for next update
|
||||
let min_peer_timeout = self.peers.iter().map(|p| p.1.peer_timeout).min().unwrap_or(DEFAULT_PEER_TIMEOUT);
|
||||
let interval = min(self.update_freq as u16, max(min_peer_timeout / 2 - 60, 1));
|
||||
self.next_peers = now + Time::from(interval);
|
||||
}
|
||||
self.reconnect_to_peers()?;
|
||||
if self.next_stats_out < now {
|
||||
// Write out the statistics
|
||||
self.write_out_stats().map_err(|err| Error::FileIo("Failed to write stats file", err))?;
|
||||
self.send_stats_to_statsd()?;
|
||||
self.next_stats_out = now + STATS_INTERVAL;
|
||||
self.traffic.period(Some(5));
|
||||
}
|
||||
if let Some(peers) = self.beacon_serializer.get_cmd_results() {
|
||||
debug!("Loaded beacon with peers: {:?}", peers);
|
||||
for peer in peers {
|
||||
self.connect_sock(peer)?;
|
||||
}
|
||||
}
|
||||
if self.next_beacon < now {
|
||||
self.store_beacon()?;
|
||||
self.load_beacon()?;
|
||||
self.next_beacon = now + Time::from(self.config.beacon_interval);
|
||||
}
|
||||
// TODO: sync peer_crypto
|
||||
self.table.sync();
|
||||
self.traffic.sync();
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn crypto_housekeep(&mut self) -> Result<(), Error> {
|
||||
let mut msg = MsgBuffer::new(SPACE_BEFORE);
|
||||
let mut del: SmallVec<[SocketAddr; 4]> = smallvec![];
|
||||
for addr in self.pending_inits.keys().copied().collect::<SmallVec<[SocketAddr; 4]>>() {
|
||||
msg.clear();
|
||||
if self.pending_inits.get_mut(&addr).unwrap().every_second(&mut msg).is_err() {
|
||||
del.push(addr)
|
||||
} else if !msg.is_empty() {
|
||||
self.send_to(addr, &msg)?
|
||||
}
|
||||
}
|
||||
for addr in self.peers.keys().copied().collect::<SmallVec<[SocketAddr; 16]>>() {
|
||||
msg.clear();
|
||||
self.peers.get_mut(&addr).unwrap().crypto.every_second(&mut msg);
|
||||
if !msg.is_empty() {
|
||||
self.send_to(addr, &msg)?
|
||||
}
|
||||
}
|
||||
for addr in del {
|
||||
self.pending_inits.remove(&addr);
|
||||
if self.peers.remove(&addr).is_some() {
|
||||
self.connect_sock(addr)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_own_addresses(&mut self) -> io::Result<()> {
|
||||
self.own_addresses.clear();
|
||||
self.own_addresses.push(self.socket.address().map(mapped_addr)?);
|
||||
if let Some(ref pfw) = self.port_forwarding {
|
||||
self.own_addresses.push(pfw.get_internal_ip().into());
|
||||
self.own_addresses.push(pfw.get_external_ip().into());
|
||||
}
|
||||
debug!("Own addresses: {:?}", self.own_addresses);
|
||||
// TODO: detect address changes and call event
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores the beacon
|
||||
fn store_beacon(&mut self) -> Result<(), Error> {
|
||||
if let Some(ref path) = self.config.beacon_store {
|
||||
let peers: SmallVec<[SocketAddr; 3]> =
|
||||
self.own_addresses.choose_multiple(&mut thread_rng(), 3).cloned().collect();
|
||||
if let Some(path) = path.strip_prefix('|') {
|
||||
self.beacon_serializer
|
||||
.write_to_cmd(&peers, path)
|
||||
.map_err(|e| Error::BeaconIo("Failed to call beacon command", e))?;
|
||||
} else {
|
||||
self.beacon_serializer
|
||||
.write_to_file(&peers, &path)
|
||||
.map_err(|e| Error::BeaconIo("Failed to write beacon to file", e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads the beacon
|
||||
fn load_beacon(&mut self) -> Result<(), Error> {
|
||||
let peers;
|
||||
if let Some(ref path) = self.config.beacon_load {
|
||||
if let Some(path) = path.strip_prefix('|') {
|
||||
self.beacon_serializer
|
||||
.read_from_cmd(path, Some(50))
|
||||
.map_err(|e| Error::BeaconIo("Failed to call beacon command", e))?;
|
||||
return Ok(())
|
||||
} else {
|
||||
peers = self
|
||||
.beacon_serializer
|
||||
.read_from_file(&path, Some(50))
|
||||
.map_err(|e| Error::BeaconIo("Failed to read beacon from file", e))?;
|
||||
}
|
||||
} else {
|
||||
return Ok(())
|
||||
}
|
||||
debug!("Loaded beacon with peers: {:?}", peers);
|
||||
for peer in peers {
|
||||
self.connect_sock(peer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes out the statistics to a file
|
||||
fn write_out_stats(&mut self) -> Result<(), io::Error> {
|
||||
if let Some(ref mut f) = self.stats_file {
|
||||
debug!("Writing out stats");
|
||||
f.seek(SeekFrom::Start(0))?;
|
||||
f.set_len(0)?;
|
||||
writeln!(f, "peers:")?;
|
||||
let now = TS::now();
|
||||
for (addr, data) in &self.peers {
|
||||
writeln!(
|
||||
f,
|
||||
" - \"{}\": {{ ttl_secs: {}, crypto: {} }}",
|
||||
addr_nice(*addr),
|
||||
data.timeout - now,
|
||||
data.crypto.algorithm_name()
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
self.table.write_out(f)?;
|
||||
writeln!(f)?;
|
||||
self.traffic.write_out(f)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends the statistics to a statsd endpoint
|
||||
fn send_stats_to_statsd(&mut self) -> Result<(), Error> {
|
||||
if let Some(ref endpoint) = self.statsd_server {
|
||||
let peer_traffic = self.traffic.total_peer_traffic();
|
||||
let payload_traffic = self.traffic.total_payload_traffic();
|
||||
let dropped = &self.traffic.dropped();
|
||||
let prefix = self.config.statsd_prefix.as_ref().map(|s| s as &str).unwrap_or("vpncloud");
|
||||
let msg = StatsdMsg::new()
|
||||
.with_ns(prefix, |msg| {
|
||||
msg.add("peer_count", self.peers.len(), "g");
|
||||
msg.add("table_cache_entries", self.table.cache_len(), "g");
|
||||
msg.add("table_claims", self.table.claim_len(), "g");
|
||||
msg.with_ns("traffic", |msg| {
|
||||
msg.with_ns("protocol", |msg| {
|
||||
msg.with_ns("inbound", |msg| {
|
||||
msg.add("bytes", peer_traffic.in_bytes, "c");
|
||||
msg.add("packets", peer_traffic.in_packets, "c");
|
||||
});
|
||||
msg.with_ns("outbound", |msg| {
|
||||
msg.add("bytes", peer_traffic.out_bytes, "c");
|
||||
msg.add("packets", peer_traffic.out_packets, "c");
|
||||
});
|
||||
});
|
||||
msg.with_ns("payload", |msg| {
|
||||
msg.with_ns("inbound", |msg| {
|
||||
msg.add("bytes", payload_traffic.in_bytes, "c");
|
||||
msg.add("packets", payload_traffic.in_packets, "c");
|
||||
});
|
||||
msg.with_ns("outbound", |msg| {
|
||||
msg.add("bytes", payload_traffic.out_bytes, "c");
|
||||
msg.add("packets", payload_traffic.out_packets, "c");
|
||||
});
|
||||
});
|
||||
});
|
||||
msg.with_ns("invalid_protocol_traffic", |msg| {
|
||||
msg.add("bytes", dropped.in_bytes, "c");
|
||||
msg.add("packets", dropped.in_packets, "c");
|
||||
});
|
||||
msg.with_ns("dropped_payload", |msg| {
|
||||
msg.add("bytes", dropped.out_bytes, "c");
|
||||
msg.add("packets", dropped.out_packets, "c");
|
||||
});
|
||||
})
|
||||
.build();
|
||||
let msg_data = msg.as_bytes();
|
||||
let addrs = resolve(endpoint)?;
|
||||
if let Some(addr) = addrs.first() {
|
||||
match self.socket.send(msg_data, *addr) {
|
||||
Ok(written) if written == msg_data.len() => Ok(()),
|
||||
Ok(_) => Err(Error::Socket("Sent out truncated packet")),
|
||||
Err(e) => Err(Error::SocketIo("IOError when sending", e))
|
||||
}?
|
||||
} else {
|
||||
error!("Failed to resolve statsd server {}", endpoint);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reconnect_to_peers(&mut self) -> Result<(), Error> {
|
||||
let now = TS::now();
|
||||
// Connect to those reconnect_peers that are due
|
||||
for entry in self.reconnect_peers.clone() {
|
||||
if entry.next > now {
|
||||
continue
|
||||
}
|
||||
self.connect(&entry.resolved as &[SocketAddr])?;
|
||||
}
|
||||
for entry in &mut self.reconnect_peers {
|
||||
// Schedule for next second if node is connected
|
||||
for addr in &entry.resolved {
|
||||
if self.peers.contains_key(&addr) {
|
||||
entry.tries = 0;
|
||||
entry.timeout = 1;
|
||||
entry.next = now + 1;
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Resolve entries anew
|
||||
if let Some((ref address, ref mut next_resolve)) = entry.address {
|
||||
if *next_resolve <= now {
|
||||
match resolve(address as &str) {
|
||||
Ok(addrs) => entry.resolved = addrs,
|
||||
Err(_) => {
|
||||
match resolve(&format!("{}:{}", address, DEFAULT_PORT)) {
|
||||
Ok(addrs) => entry.resolved = addrs,
|
||||
Err(err) => warn!("Failed to resolve {}: {}", address, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
*next_resolve = now + RESOLVE_INTERVAL;
|
||||
}
|
||||
}
|
||||
// Ignore if next attempt is already in the future
|
||||
if entry.next > now {
|
||||
continue
|
||||
}
|
||||
// Exponential back-off: every 10 tries, the interval doubles
|
||||
entry.tries += 1;
|
||||
if entry.tries > 10 {
|
||||
entry.tries = 0;
|
||||
entry.timeout *= 2;
|
||||
}
|
||||
// Maximum interval is one hour
|
||||
if entry.timeout > MAX_RECONNECT_INTERVAL {
|
||||
entry.timeout = MAX_RECONNECT_INTERVAL;
|
||||
}
|
||||
// Schedule next connection attempt
|
||||
entry.next = now + Time::from(entry.timeout);
|
||||
}
|
||||
self.reconnect_peers.retain(|e| e.final_timeout.unwrap_or(now) >= now);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(mut self) {
|
||||
let mut buffer = MsgBuffer::new(SPACE_BEFORE);
|
||||
loop {
|
||||
let src = try_fail!(self.socket.receive(&mut buffer), "Failed to read from network socket: {}");
|
||||
match self.socket.receive(&mut buffer) {
|
||||
Err(err) => {
|
||||
if err.kind() == io::ErrorKind::TimedOut || err.kind() == io::ErrorKind::WouldBlock {
|
||||
// ok, this is a normal timeout
|
||||
} else {
|
||||
fail!("Failed to read from network socket: {}", err);
|
||||
}
|
||||
}
|
||||
Ok(src) => {
|
||||
match self.handle_message(src, &mut buffer) {
|
||||
Err(e @ Error::CryptoInitFatal(_)) => {
|
||||
debug!("Fatal crypto init error from {}: {}", src, e);
|
||||
|
@ -315,12 +701,14 @@ impl<S: Socket, D: Device, P: Protocol, TS: TimeSource> SocketThread<S, D, P, TS
|
|||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let now = TS::now();
|
||||
if self.next_housekeep < TS::now() {
|
||||
if self.next_housekeep < now {
|
||||
if let Err(e) = self.housekeep() {
|
||||
error!("{}", e)
|
||||
}
|
||||
self.next_housekeep = TS::now() + 1
|
||||
self.next_housekeep = now + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
use crate::error::Error;
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
os::unix::fs::PermissionsExt
|
||||
};
|
||||
|
||||
const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz");
|
||||
const SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud@.service");
|
||||
const WS_PROXY_SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud-wsproxy.service");
|
||||
const EXAMPLE_CONFIG: &[u8] = include_bytes!("../assets/example.net.disabled");
|
||||
|
||||
pub fn install() -> Result<(), Error> {
|
||||
env::current_exe()
|
||||
.and_then(|p| fs::copy(p, "/usr/bin/vpncloud"))
|
||||
.map_err(|e| Error::FileIo("Failed to copy binary", e))?;
|
||||
fs::set_permissions("/usr/bin/vpncloud", fs::Permissions::from_mode(755))
|
||||
.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::set_permissions("/etc/vpncloud", fs::Permissions::from_mode(600))
|
||||
.map_err(|e| Error::FileIo("Failed to set permissions for config folder", e))?;
|
||||
File::create("/etc/vpncloud/example.net.disabled")
|
||||
.and_then(|mut f| f.write_all(EXAMPLE_CONFIG))
|
||||
.map_err(|e| Error::FileIo("Failed to create example config", e))?;
|
||||
File::create("/usr/share/man/man1/vpncloud.1.gz")
|
||||
.and_then(|mut f| f.write_all(MANPAGE))
|
||||
.map_err(|e| Error::FileIo("Failed to create manpage", e))?;
|
||||
File::create("/lib/systemd/system/vpncloud@.service")
|
||||
.and_then(|mut f| f.write_all(SERVICE_FILE))
|
||||
.map_err(|e| Error::FileIo("Failed to create service file", e))?;
|
||||
File::create("/lib/systemd/system/vpncloud-wsproxy.service")
|
||||
.and_then(|mut f| f.write_all(WS_PROXY_SERVICE_FILE))
|
||||
.map_err(|e| Error::FileIo("Failed to create wsporxy service file", e))?;
|
||||
info!("Install successful");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uninstall() -> Result<(), Error> {
|
||||
fs::remove_file("/etc/vpncloud/example.net.disabled").map_err(|e| Error::FileIo("Failed to remove binary", 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")
|
||||
.map_err(|e| Error::FileIo("Failed to remove service file", e))?;
|
||||
fs::remove_file("/lib/systemd/system/vpncloud-wsproxy.service")
|
||||
.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))?;
|
||||
info!("Uninstall successful");
|
||||
Ok(())
|
||||
}
|
27
src/main.rs
27
src/main.rs
|
@ -27,14 +27,16 @@ pub mod port_forwarding;
|
|||
pub mod table;
|
||||
pub mod traffic;
|
||||
pub mod types;
|
||||
#[cfg(feature = "wizard")] pub mod wizard;
|
||||
#[cfg(feature = "websocket")] pub mod wsproxy;
|
||||
#[cfg(feature = "installer")] pub mod installer;
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
use std::{
|
||||
fs::{self, File, Permissions},
|
||||
io::{self, Write},
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
net::{Ipv4Addr},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::Path,
|
||||
process,
|
||||
|
@ -48,7 +50,7 @@ use crate::{
|
|||
config::{Args, Command, Config, DEFAULT_PORT},
|
||||
crypto::Crypto,
|
||||
device::{Device, TunTapDevice, Type},
|
||||
net::Socket,
|
||||
net::{Socket, NetSocket},
|
||||
oldconfig::OldConfigFile,
|
||||
payload::Protocol,
|
||||
util::SystemTimeSource,
|
||||
|
@ -183,6 +185,7 @@ fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
|
|||
Some(file)
|
||||
}
|
||||
};
|
||||
let ifname = device.ifname().to_string();
|
||||
let mut cloud =
|
||||
GenericCloud::<TunTapDevice, P, S, SystemTimeSource>::new(&config, socket, device, port_forwarding, stats_file);
|
||||
for mut addr in config.peers {
|
||||
|
@ -190,8 +193,7 @@ fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
|
|||
// : not present or only in IPv6 address
|
||||
addr = format!("{}:{}", addr, DEFAULT_PORT)
|
||||
}
|
||||
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
|
||||
cloud.add_reconnect_peer(addr);
|
||||
try_fail!(cloud.add_peer(addr.clone()), "Failed to send message to {}: {}", &addr);
|
||||
}
|
||||
if config.daemonize {
|
||||
info!("Running process as daemon");
|
||||
|
@ -221,7 +223,7 @@ fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
|
|||
}
|
||||
cloud.run();
|
||||
if let Some(script) = config.ifdown {
|
||||
run_script(&script, cloud.ifname());
|
||||
run_script(&script, &ifname);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,12 +274,23 @@ fn main() {
|
|||
}
|
||||
Command::Completion { shell } => {
|
||||
Args::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut io::stdout());
|
||||
return
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
Command::WsProxy { listen } => {
|
||||
try_fail!(wsproxy::run_proxy(&listen), "Failed to run websocket proxy: {:?}");
|
||||
}
|
||||
#[cfg(feature = "wizard")]
|
||||
Command::Config { name } => {
|
||||
try_fail!(wizard::configure(name), "Wizard failed: {}");
|
||||
}
|
||||
#[cfg(feature = "installer")]
|
||||
Command::Install { uninstall } => {
|
||||
if uninstall {
|
||||
try_fail!(installer::uninstall(), "Uninstall failed: {}");
|
||||
} else {
|
||||
try_fail!(installer::install(), "Install failed: {}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -315,7 +328,7 @@ fn main() {
|
|||
}
|
||||
return
|
||||
}
|
||||
let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
|
||||
let socket = try_fail!(NetSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
|
||||
match config.device_type {
|
||||
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||
Type::Tun => run::<payload::Packet, _>(config, socket)
|
||||
|
|
71
src/net.rs
71
src/net.rs
|
@ -2,17 +2,21 @@
|
|||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
|
||||
use crate::port_forwarding::PortForwarding;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
io::{self, ErrorKind},
|
||||
net::{IpAddr, SocketAddr, UdpSocket, Ipv6Addr},
|
||||
net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket},
|
||||
os::unix::io::{AsRawFd, RawFd},
|
||||
sync::atomic::{AtomicBool, Ordering}
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc
|
||||
},
|
||||
time::Duration
|
||||
};
|
||||
|
||||
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
|
||||
use crate::port_forwarding::PortForwarding;
|
||||
|
||||
pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
|
||||
// HOT PATH
|
||||
match addr {
|
||||
|
@ -27,7 +31,7 @@ pub fn get_ip() -> IpAddr {
|
|||
s.local_addr().unwrap().ip()
|
||||
}
|
||||
|
||||
pub trait Socket: AsRawFd + Sized {
|
||||
pub trait Socket: AsRawFd + Sized + Clone + Send + 'static {
|
||||
fn listen(addr: &str) -> Result<Self, io::Error>;
|
||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error>;
|
||||
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error>;
|
||||
|
@ -47,25 +51,42 @@ pub fn parse_listen(addr: &str) -> SocketAddr {
|
|||
}
|
||||
}
|
||||
|
||||
impl Socket for UdpSocket {
|
||||
pub struct NetSocket(UdpSocket);
|
||||
|
||||
impl Clone for NetSocket {
|
||||
fn clone(&self) -> Self {
|
||||
Self(try_fail!(self.0.try_clone(), "Failed to clone socket: {}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for NetSocket {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Socket for NetSocket {
|
||||
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||
let addr = parse_listen(addr);
|
||||
UdpSocket::bind(addr)
|
||||
Ok(NetSocket(UdpSocket::bind(addr).and_then(|s| {
|
||||
s.set_read_timeout(Some(Duration::from_secs(1)))?;
|
||||
Ok(s)
|
||||
})?))
|
||||
}
|
||||
|
||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||
buffer.clear();
|
||||
let (size, addr) = self.recv_from(buffer.buffer())?;
|
||||
let (size, addr) = self.0.recv_from(buffer.buffer())?;
|
||||
buffer.set_length(size);
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error> {
|
||||
self.send_to(data, addr)
|
||||
self.0.send_to(data, addr)
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||
let mut addr = self.local_addr()?;
|
||||
let mut addr = self.0.local_addr()?;
|
||||
addr.set_ip(get_ip());
|
||||
Ok(addr)
|
||||
}
|
||||
|
@ -79,22 +100,24 @@ thread_local! {
|
|||
static MOCK_SOCKET_NAT: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MockSocket {
|
||||
nat: bool,
|
||||
nat_peers: HashMap<SocketAddr, Time>,
|
||||
nat_peers: Arc<Mutex<HashMap<SocketAddr, Time>>>,
|
||||
address: SocketAddr,
|
||||
outbound: VecDeque<(SocketAddr, Vec<u8>)>,
|
||||
inbound: VecDeque<(SocketAddr, Vec<u8>)>
|
||||
outbound: Arc<Mutex<VecDeque<(SocketAddr, Vec<u8>)>>>,
|
||||
inbound: Arc<Mutex<VecDeque<(SocketAddr, Vec<u8>)>>>
|
||||
}
|
||||
|
||||
impl MockSocket {
|
||||
pub fn new(address: SocketAddr) -> Self {
|
||||
Self {
|
||||
nat: Self::get_nat(),
|
||||
nat_peers: HashMap::new(),
|
||||
nat_peers: Default::default(),
|
||||
address,
|
||||
outbound: VecDeque::with_capacity(10),
|
||||
inbound: VecDeque::with_capacity(10)
|
||||
outbound: Arc::new(Mutex::new(VecDeque::with_capacity(10))),
|
||||
inbound: Arc::new(Mutex::new(VecDeque::with_capacity(10)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,12 +131,12 @@ impl MockSocket {
|
|||
|
||||
pub fn put_inbound(&mut self, from: SocketAddr, data: Vec<u8>) -> bool {
|
||||
if !self.nat {
|
||||
self.inbound.push_back((from, data));
|
||||
self.inbound.lock().push_back((from, data));
|
||||
return true
|
||||
}
|
||||
if let Some(timeout) = self.nat_peers.get(&from) {
|
||||
if let Some(timeout) = self.nat_peers.lock().get(&from) {
|
||||
if *timeout >= MockTimeSource::now() {
|
||||
self.inbound.push_back((from, data));
|
||||
self.inbound.lock().push_back((from, data));
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +145,7 @@ impl MockSocket {
|
|||
}
|
||||
|
||||
pub fn pop_outbound(&mut self) -> Option<(SocketAddr, Vec<u8>)> {
|
||||
self.outbound.pop_front()
|
||||
self.outbound.lock().pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +161,7 @@ impl Socket for MockSocket {
|
|||
}
|
||||
|
||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||
if let Some((addr, data)) = self.inbound.pop_front() {
|
||||
if let Some((addr, data)) = self.inbound.lock().pop_front() {
|
||||
buffer.clear();
|
||||
buffer.set_length(data.len());
|
||||
buffer.message_mut().copy_from_slice(&data);
|
||||
|
@ -149,9 +172,9 @@ impl Socket for MockSocket {
|
|||
}
|
||||
|
||||
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error> {
|
||||
self.outbound.push_back((addr, data.into()));
|
||||
self.outbound.lock().push_back((addr, data.into()));
|
||||
if self.nat {
|
||||
self.nat_peers.insert(addr, MockTimeSource::now() + 300);
|
||||
self.nat_peers.lock().insert(addr, MockTimeSource::now() + 300);
|
||||
}
|
||||
Ok(data.len())
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use crate::{error::Error, types::Address};
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
pub trait Protocol: Sized {
|
||||
pub trait Protocol: Sized + Send + 'static {
|
||||
fn parse(_: &[u8]) -> Result<(Address, Address), Error>;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use super::{
|
|||
};
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TrafficEntry {
|
||||
pub out_bytes_total: u64,
|
||||
pub out_packets_total: usize,
|
||||
|
|
|
@ -0,0 +1,512 @@
|
|||
use crate::{config::Config, crypto::Crypto, device, types::Mode};
|
||||
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Password, Select};
|
||||
use ring::aead;
|
||||
use std::{collections::HashMap, fs, io, os::unix::fs::PermissionsExt, path::Path};
|
||||
|
||||
const MODE_SIMPLE: usize = 0;
|
||||
const MODE_ADVANCED: usize = 1;
|
||||
const MODE_EXPERT: usize = 2;
|
||||
|
||||
fn str_list(s: String) -> Vec<String> {
|
||||
if s.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
s.split(',').map(|k| k.trim().to_string()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn str_opt(s: String) -> Option<String> {
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_connectivity(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
if mode >= MODE_ADVANCED {
|
||||
config.listen =
|
||||
Input::with_theme(theme).with_prompt("Listen address").default(config.listen.clone()).interact_text()?;
|
||||
}
|
||||
config.peers = str_list(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Peer addresses (comma separated)")
|
||||
.default(config.peers.join(","))
|
||||
.interact_text()?
|
||||
);
|
||||
if mode >= MODE_ADVANCED {
|
||||
config.port_forwarding = Confirm::with_theme(theme)
|
||||
.with_prompt("Enable automatic port forwarding?")
|
||||
.default(config.port_forwarding)
|
||||
.interact()?;
|
||||
}
|
||||
if mode == MODE_EXPERT {
|
||||
config.peer_timeout = Input::with_theme(theme)
|
||||
.with_prompt("Peer timeout (in seconds)")
|
||||
.default(config.peer_timeout)
|
||||
.interact_text()?;
|
||||
let val = Input::with_theme(theme)
|
||||
.with_prompt("Keepalive interval (in seconds, 0 for default)")
|
||||
.default(config.keepalive.unwrap_or_default())
|
||||
.interact_text()?;
|
||||
config.keepalive = if val == 0 { None } else { Some(val) };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
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())
|
||||
&& !Confirm::with_theme(theme).with_prompt("Create new crypto config?").default(false).interact()?
|
||||
{
|
||||
return Ok(())
|
||||
}
|
||||
let mut use_password = true;
|
||||
if mode >= MODE_ADVANCED {
|
||||
use_password = Select::with_theme(theme)
|
||||
.with_prompt("Crypto configuration method")
|
||||
.items(&["Simple (Password)", "Complex (Key pair)"])
|
||||
.default(if config.crypto.private_key.is_some() { 1 } else { 0 })
|
||||
.interact()?
|
||||
== 0
|
||||
}
|
||||
if use_password {
|
||||
config.crypto.password = Some(
|
||||
Password::with_theme(theme)
|
||||
.with_prompt("Password")
|
||||
.with_confirmation("Confirm password", "Passwords do not match")
|
||||
.interact()?
|
||||
);
|
||||
config.crypto.private_key = None;
|
||||
config.crypto.public_key = None;
|
||||
config.crypto.trusted_keys = vec![];
|
||||
} else {
|
||||
config.crypto.password = None;
|
||||
let (priv_key, pub_key) = match Select::with_theme(theme)
|
||||
.with_prompt("Specify key pair")
|
||||
.items(&["Generate new key pair", "Enter private key", "Generate from password"])
|
||||
.default(0)
|
||||
.interact()?
|
||||
{
|
||||
0 => {
|
||||
let (priv_key, pub_key) = Crypto::generate_keypair(None);
|
||||
info!("Private key: {}", priv_key);
|
||||
info!("Public key: {}", pub_key);
|
||||
(priv_key, pub_key)
|
||||
}
|
||||
1 => {
|
||||
let priv_key = Password::with_theme(theme)
|
||||
.with_prompt("Private key")
|
||||
.with_confirmation("Confirm private key", "Keys do not match")
|
||||
.interact()?;
|
||||
let pub_key = try_fail!(Crypto::public_key_from_private_key(&priv_key), "Invalid private key: {:?}");
|
||||
info!("Public key: {}", pub_key);
|
||||
(priv_key, pub_key)
|
||||
}
|
||||
2 => {
|
||||
let password = Password::with_theme(theme)
|
||||
.with_prompt("Password")
|
||||
.with_confirmation("Confirm password", "Passwords do not match")
|
||||
.interact()?;
|
||||
let (priv_key, pub_key) = Crypto::generate_keypair(Some(&password));
|
||||
info!("Private key: {}", priv_key);
|
||||
info!("Public key: {}", pub_key);
|
||||
(priv_key, pub_key)
|
||||
}
|
||||
_ => unreachable!()
|
||||
};
|
||||
config.crypto.trusted_keys = str_list(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Trusted keys (public keys, comma separated)")
|
||||
.default(pub_key.clone())
|
||||
.interact_text()?
|
||||
);
|
||||
config.crypto.private_key = Some(priv_key);
|
||||
config.crypto.public_key = Some(pub_key);
|
||||
}
|
||||
if mode == MODE_EXPERT {
|
||||
let (unencrypted, allowed_algos) = Crypto::parse_algorithms(&config.crypto.algorithms)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid crypto algorithms"))?;
|
||||
let algos = MultiSelect::with_theme(theme)
|
||||
.with_prompt("Allowed encryption algorithms (select multiple)")
|
||||
.items_checked(&[
|
||||
("Unencrypted (dangerous)", unencrypted),
|
||||
("AES-128 in GCM mode", allowed_algos.contains(&&aead::AES_128_GCM)),
|
||||
("AES-256 in GCM mode", allowed_algos.contains(&&aead::AES_256_GCM)),
|
||||
("ChaCha20-Poly1305 (RFC 7539)", allowed_algos.contains(&&aead::CHACHA20_POLY1305))
|
||||
])
|
||||
.interact()?;
|
||||
config.crypto.algorithms = vec![];
|
||||
for (id, name) in &[(0, "PLAIN"), (1, "AES128"), (2, "AES256"), (3, "CHACHA20")] {
|
||||
if algos.contains(id) {
|
||||
config.crypto.algorithms.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_device(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
if mode >= MODE_ADVANCED {
|
||||
config.device_type = match Select::with_theme(theme)
|
||||
.with_prompt("Device type")
|
||||
.items(&["Tun (IP based)", "Tap (Ethernet based)"])
|
||||
.default(if config.device_type == device::Type::Tun { 0 } else { 1 })
|
||||
.interact()?
|
||||
{
|
||||
0 => device::Type::Tun,
|
||||
1 => device::Type::Tap,
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
if mode == MODE_EXPERT {
|
||||
config.device_name =
|
||||
Input::with_theme(theme).with_prompt("Device name").default(config.device_name.clone()).interact_text()?;
|
||||
config.device_path = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Device path (empty for default)")
|
||||
.default(config.device_path.as_ref().cloned().unwrap_or_default())
|
||||
.interact_text()?
|
||||
);
|
||||
config.fix_rp_filter = Confirm::with_theme(theme)
|
||||
.with_prompt("Automatically fix insecure rp_filter settings")
|
||||
.default(config.fix_rp_filter)
|
||||
.interact()?;
|
||||
config.mode = match Select::with_theme(theme)
|
||||
.with_prompt("Operation mode")
|
||||
.items(&["Normal", "Router", "Switch", "Hub"])
|
||||
.default(match config.mode {
|
||||
Mode::Normal => 0,
|
||||
Mode::Router => 1,
|
||||
Mode::Switch => 2,
|
||||
Mode::Hub => 3
|
||||
})
|
||||
.interact()?
|
||||
{
|
||||
0 => Mode::Normal,
|
||||
1 => Mode::Router,
|
||||
2 => Mode::Switch,
|
||||
3 => Mode::Hub,
|
||||
_ => unreachable!()
|
||||
};
|
||||
if config.mode == Mode::Switch {
|
||||
config.switch_timeout = Input::with_theme(theme)
|
||||
.with_prompt("Switch timeout (in seconds")
|
||||
.default(config.switch_timeout)
|
||||
.interact_text()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_addresses(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
config.ip = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Virtual IP address (e.g. 10.0.0.1, leave empty for none)")
|
||||
.allow_empty(true)
|
||||
.default(config.ip.as_ref().cloned().unwrap_or_default())
|
||||
.interact_text()?
|
||||
);
|
||||
if config.device_type == device::Type::Tun {
|
||||
if mode >= MODE_ADVANCED {
|
||||
config.auto_claim = Confirm::with_theme(theme)
|
||||
.with_prompt("Automatically claim IP set on virtual interface?")
|
||||
.default(config.auto_claim)
|
||||
.interact()?;
|
||||
}
|
||||
if mode == MODE_EXPERT {
|
||||
config.claims = str_list(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Claim additional addresses (e.g. 10.0.0.0/24, comma separated, leave empty for none)")
|
||||
.allow_empty(true)
|
||||
.default(config.claims.join(","))
|
||||
.interact_text()?
|
||||
);
|
||||
}
|
||||
} else {
|
||||
config.claims = vec![];
|
||||
}
|
||||
if mode == MODE_EXPERT {
|
||||
config.ifup = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Interface setup command (leave empty for none)")
|
||||
.allow_empty(true)
|
||||
.default(config.ifup.as_ref().cloned().unwrap_or_default())
|
||||
.interact_text()?
|
||||
);
|
||||
config.ifdown = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Interface tear down command (leave empty for none)")
|
||||
.allow_empty(true)
|
||||
.default(config.ifdown.as_ref().cloned().unwrap_or_default())
|
||||
.interact_text()?
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_beacon(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
if mode == MODE_EXPERT
|
||||
&& Confirm::with_theme(theme)
|
||||
.with_prompt("Configure beacons?")
|
||||
.default(config.beacon_load.is_some() || config.beacon_store.is_some())
|
||||
.interact()?
|
||||
{
|
||||
config.beacon_store = match Select::with_theme(theme)
|
||||
.with_prompt("How to store beacons")
|
||||
.items(&["Do not store beacons", "Store to file", "Execute command"])
|
||||
.default(if let Some(v) = &config.beacon_store {
|
||||
if v.starts_with('|') {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
0
|
||||
})
|
||||
.interact()?
|
||||
{
|
||||
0 => None,
|
||||
1 => {
|
||||
Some(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("File path")
|
||||
.default(config.beacon_store.clone().unwrap_or_default())
|
||||
.interact_text()?
|
||||
)
|
||||
}
|
||||
2 => {
|
||||
Some(format!(
|
||||
"|{}",
|
||||
Input::<String>::with_theme(theme)
|
||||
.with_prompt("Command")
|
||||
.default(config.beacon_store.clone().unwrap_or_default().trim_start_matches('|').to_string())
|
||||
.interact_text()?
|
||||
))
|
||||
}
|
||||
_ => unreachable!()
|
||||
};
|
||||
config.beacon_load = match Select::with_theme(theme)
|
||||
.with_prompt("How to load beacons")
|
||||
.items(&["Do not load beacons", "Load from file", "Execute command"])
|
||||
.default(if let Some(v) = &config.beacon_load {
|
||||
if v.starts_with('|') {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
} else {
|
||||
0
|
||||
})
|
||||
.interact()?
|
||||
{
|
||||
0 => None,
|
||||
1 => {
|
||||
Some(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("File path")
|
||||
.default(config.beacon_load.clone().unwrap_or_default())
|
||||
.interact_text()?
|
||||
)
|
||||
}
|
||||
2 => {
|
||||
Some(format!(
|
||||
"|{}",
|
||||
Input::<String>::with_theme(theme)
|
||||
.with_prompt("Command")
|
||||
.default(config.beacon_load.clone().unwrap_or_default().trim_start_matches('|').to_string())
|
||||
.interact_text()?
|
||||
))
|
||||
}
|
||||
_ => unreachable!()
|
||||
};
|
||||
config.beacon_interval = Input::with_theme(theme)
|
||||
.with_prompt("Beacon interval (in seconds)")
|
||||
.default(config.beacon_interval)
|
||||
.interact_text()?;
|
||||
config.beacon_password = str_opt(
|
||||
Password::with_theme(theme)
|
||||
.with_prompt("Beacon password (leave empty for none)")
|
||||
.with_confirmation("Confirm password", "Passwords do not match")
|
||||
.allow_empty_password(true)
|
||||
.interact()?
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_stats(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
if mode >= MODE_ADVANCED {
|
||||
config.stats_file = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Write stats to file (empty to disable)")
|
||||
.default(config.stats_file.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
}
|
||||
if mode == MODE_EXPERT {
|
||||
if Confirm::with_theme(theme)
|
||||
.with_prompt("Send statistics to statsd server?")
|
||||
.default(config.statsd_server.is_some())
|
||||
.interact()?
|
||||
{
|
||||
config.statsd_server = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Statsd server URL")
|
||||
.default(config.statsd_server.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
config.statsd_prefix = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Statsd prefix")
|
||||
.default(config.statsd_prefix.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
} else {
|
||||
config.statsd_server = None;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_process(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
if mode == MODE_EXPERT {
|
||||
config.user = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Run as different user (empty to disable)")
|
||||
.default(config.user.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
config.group = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Run as different group (empty to disable)")
|
||||
.default(config.group.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
config.pid_file = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Write process id to file (empty to disable)")
|
||||
.default(config.pid_file.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_hooks(config: &mut Config, mode: usize, theme: &ColorfulTheme) -> Result<(), io::Error> {
|
||||
if mode == MODE_EXPERT {
|
||||
if Confirm::with_theme(theme)
|
||||
.with_prompt("Set hooks to react on events?")
|
||||
.default(config.hook.is_some() || !config.hooks.is_empty())
|
||||
.interact()?
|
||||
{
|
||||
config.hook = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt("Command to execute for all events (empty to disable)")
|
||||
.default(config.hook.clone().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
);
|
||||
let mut hooks: HashMap<String, String> = Default::default();
|
||||
for event in &[
|
||||
"peer_connecting",
|
||||
"peer_connected",
|
||||
"peer_disconnected",
|
||||
"device_setup",
|
||||
"device_configured",
|
||||
"vpn_started",
|
||||
"vpn_shutdown"
|
||||
] {
|
||||
if let Some(cmd) = str_opt(
|
||||
Input::with_theme(theme)
|
||||
.with_prompt(format!("Command to execute for event '{}' (empty to disable)", event))
|
||||
.default(config.hooks.get(*event).cloned().unwrap_or_default())
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
) {
|
||||
hooks.insert(event.to_string(), cmd);
|
||||
}
|
||||
}
|
||||
config.hooks = hooks;
|
||||
} else {
|
||||
config.hook = None;
|
||||
config.hooks = Default::default();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(name: Option<String>) -> Result<(), io::Error> {
|
||||
let theme = ColorfulTheme::default();
|
||||
|
||||
let name = if let Some(name) = name {
|
||||
name
|
||||
} else {
|
||||
let mut names = vec![];
|
||||
for file in fs::read_dir("/etc/vpncloud")? {
|
||||
names.push(file?.path().file_stem().unwrap().to_str().unwrap().to_string());
|
||||
}
|
||||
let selection =
|
||||
Select::with_theme(&theme).with_prompt("Which network?").item("New network").items(&names).interact()?;
|
||||
if selection > 0 {
|
||||
names[selection - 1].clone()
|
||||
} else {
|
||||
Input::with_theme(&theme).with_prompt("Network name").interact_text()?
|
||||
}
|
||||
};
|
||||
|
||||
let mut config = Config::default();
|
||||
let file = Path::new("/etc/vpncloud").join(format!("{}.net", name));
|
||||
if file.exists() {
|
||||
let f = fs::File::open(&file)?;
|
||||
let config_file = serde_yaml::from_reader(f)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse config file"))?;
|
||||
config.merge_file(config_file);
|
||||
}
|
||||
if file.parent().unwrap().metadata()?.permissions().readonly() {
|
||||
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "Config file not writable"))
|
||||
}
|
||||
|
||||
loop {
|
||||
let mode = Select::with_theme(&theme)
|
||||
.with_prompt("Configuration mode")
|
||||
.items(&["Simple (minimal options)", "Advanced (some more options)", "Expert (all options)"])
|
||||
.default(MODE_SIMPLE)
|
||||
.interact()?;
|
||||
|
||||
configure_connectivity(&mut config, mode, &theme)?;
|
||||
configure_crypto(&mut config, mode, &theme)?;
|
||||
configure_device(&mut config, mode, &theme)?;
|
||||
configure_addresses(&mut config, mode, &theme)?;
|
||||
configure_beacon(&mut config, mode, &theme)?;
|
||||
configure_stats(&mut config, mode, &theme)?;
|
||||
configure_process(&mut config, mode, &theme)?;
|
||||
configure_hooks(&mut config, mode, &theme)?;
|
||||
if Confirm::with_theme(&theme).with_prompt("Finish configuration?").default(true).interact()? {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if Confirm::with_theme(&theme).with_prompt("Save config?").default(true).interact()? {
|
||||
let config_file = config.into_config_file();
|
||||
let f = fs::File::create(&file)?;
|
||||
serde_yaml::to_writer(f, &config_file)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse config file"))?;
|
||||
fs::set_permissions(file, fs::Permissions::from_mode(600))?;
|
||||
println!();
|
||||
println!("Use the following commands to control your VPN:");
|
||||
println!(" start the VPN: sudo service vpncloud@{0} start", name);
|
||||
println!(" stop the VPN: sudo service vpncloud@{0} stop", name);
|
||||
println!(" get the status: sudo service vpncloud@{0} status", name);
|
||||
println!(" add VPN to autostart: sudo sysctl enable vpncloud@{0}", name);
|
||||
println!(" remove VPN from autostart: sudo sysctl disable vpncloud@{0}", name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -13,6 +13,7 @@ use std::{
|
|||
io::{self, Cursor, Read, Write},
|
||||
net::{Ipv6Addr, SocketAddr, SocketAddrV6, TcpListener, TcpStream, UdpSocket},
|
||||
os::unix::io::{AsRawFd, RawFd},
|
||||
sync::Arc,
|
||||
thread::spawn
|
||||
};
|
||||
use tungstenite::{client::AutoStream, connect, protocol::WebSocket, server::accept, Message};
|
||||
|
@ -108,17 +109,21 @@ pub fn run_proxy(listen: &str) -> Result<(), io::Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProxyConnection {
|
||||
addr: SocketAddr,
|
||||
socket: WebSocket<AutoStream>
|
||||
socket: Arc<WebSocket<AutoStream>>
|
||||
}
|
||||
|
||||
impl ProxyConnection {
|
||||
fn read_message(&mut self) -> Result<Vec<u8>, io::Error> {
|
||||
loop {
|
||||
unimplemented!();
|
||||
/*
|
||||
if let Message::Binary(data) = io_error!(self.socket.read_message(), "Failed to read from ws proxy: {}")? {
|
||||
return Ok(data)
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +140,7 @@ impl Socket for ProxyConnection {
|
|||
let (mut socket, _) = io_error!(connect(parsed_url), "Failed to connect to URL {}: {}", url)?;
|
||||
socket.get_mut().set_nodelay(true)?;
|
||||
let addr = "0.0.0.0:0".parse::<SocketAddr>().unwrap();
|
||||
let mut con = ProxyConnection { addr, socket };
|
||||
let mut con = ProxyConnection { addr, socket: Arc::new(socket) };
|
||||
let addr_data = con.read_message()?;
|
||||
con.addr = read_addr(Cursor::new(&addr_data))?;
|
||||
Ok(con)
|
||||
|
@ -153,8 +158,11 @@ impl Socket for ProxyConnection {
|
|||
let mut msg = Vec::with_capacity(data.len() + 18);
|
||||
write_addr(addr, &mut msg)?;
|
||||
msg.write_all(data)?;
|
||||
unimplemented!();
|
||||
/*
|
||||
io_error!(self.socket.write_message(Message::Binary(msg)), "Failed to write to ws proxy: {}")?;
|
||||
Ok(data.len())
|
||||
*/
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||
|
|
Loading…
Reference in New Issue