mirror of https://github.com/dswd/vpncloud.git
Compare commits
No commits in common. "master" and "v0.4.1" have entirely different histories.
|
@ -1,12 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [dswd]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: dswd
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/dswd73']
|
|
@ -1,27 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### Details of your setup
|
||||
|
||||
**VpnCloud version:**
|
||||
|
||||
**Description of your VPN setup**
|
||||
- How many nodes?
|
||||
- Any NAT involved?
|
||||
- Any custom routing?
|
||||
|
||||
**Config file contents**
|
||||
normally in `/etc/vpncloud/MYNET.net`
|
||||
:bangbang: make sure to mask (`***`) passwords and public IPs/hostnames :bangbang:
|
||||
|
||||
**Log entries**
|
||||
normally in `/var/log/vpncloud.MYNET.log`
|
|
@ -1,8 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: VpnCloud Documentation
|
||||
url: https://vpncloud.ddswd.de/docs
|
||||
about: Please check the documentation before asking how things work.
|
||||
- name: Discussion Group on GitHub
|
||||
url: https://github.com/dswd/vpncloud/discussions
|
||||
about: Please discuss or ask anything there that is not an issue.
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
** Describe the high-level goal you want to achieve **
|
||||
|
||||
I would like to...
|
||||
|
||||
** Describe the proposed solution **
|
||||
|
||||
I propose to...
|
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
name: Other issue
|
||||
about: Any other issue
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Describe your issue
|
||||
|
||||
👉 Please use the [Discussion group](https://github.com/dswd/vpncloud/discussions) if you...
|
||||
* just want to ask a question
|
||||
* want to discuss a new feature
|
|
@ -1,20 +0,0 @@
|
|||
FROM ubuntu:16.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
gcc-arm-linux-gnueabi \
|
||||
libc6-dev-arm64-cross \
|
||||
libc6-dev-armhf-cross \
|
||||
libc6-dev-armel-cross \
|
||||
libc6-dev-i386 \
|
||||
gcc-5-multilib \
|
||||
asciidoctor \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ADD entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT /entrypoint.sh
|
|
@ -1,5 +0,0 @@
|
|||
name: 'build-deb'
|
||||
description: 'Create deb packages'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
TOOLCHAIN=$(grep -e '^toolchain =' Cargo.toml | sed -e 's/toolchain = "\(.*\)"/\1/')
|
||||
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
DEB_VERSION=$(echo "$VERSION" | sed -e 's/-/~/g')
|
||||
|
||||
ln -s asm-generic/ /usr/include/asm
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${TOOLCHAIN}
|
||||
source $HOME/.cargo/env
|
||||
|
||||
rustup target add i686-unknown-linux-gnu
|
||||
rustup target add armv5te-unknown-linux-gnueabi
|
||||
rustup target add armv7-unknown-linux-gnueabihf
|
||||
rustup target add aarch64-unknown-linux-gnu
|
||||
|
||||
cargo install cargo-deb
|
||||
|
||||
mkdir dist
|
||||
|
||||
build_deb() {
|
||||
ARCH=$1
|
||||
TARGET=$2
|
||||
cargo deb --target ${TARGET}
|
||||
cp target/${TARGET}/debian/vpncloud_${DEB_VERSION}_${ARCH}.deb dist/vpncloud_${DEB_VERSION}_${ARCH}.deb
|
||||
}
|
||||
|
||||
cargo deb
|
||||
cp target/debian/vpncloud_${DEB_VERSION}_amd64.deb dist/vpncloud_${DEB_VERSION}_amd64.deb
|
||||
|
||||
build_deb i386 i686-unknown-linux-gnu
|
||||
build_deb armhf armv7-unknown-linux-gnueabihf
|
||||
build_deb armel armv5te-unknown-linux-gnueabi
|
||||
build_deb arm64 aarch64-unknown-linux-gnu
|
|
@ -1,13 +0,0 @@
|
|||
FROM centos:7
|
||||
|
||||
RUN yum groupinstall -y 'Development Tools'
|
||||
RUN yum install -y ruby && gem install asciidoctor -v 2.0.10
|
||||
RUN yum install -y libstdc++-*.i686 \
|
||||
&& yum install -y glibc-*.i686 \
|
||||
&& yum install -y libgcc.i686
|
||||
|
||||
RUN ln -s /usr/bin/gcc /usr/bin/i686-linux-gnu-gcc
|
||||
|
||||
ADD entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT /entrypoint.sh
|
|
@ -1,5 +0,0 @@
|
|||
name: 'build-deb'
|
||||
description: 'Create deb packages'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
|
@ -1,41 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
TOOLCHAIN=$(grep -e '^toolchain =' Cargo.toml | sed -e 's/toolchain = "\(.*\)"/\1/')
|
||||
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
if echo "$VERSION" | fgrep -q "-"; then
|
||||
RPM_VERSION=$(echo "$VERSION" | sed -e 's/-/-0./g')
|
||||
else
|
||||
RPM_VERSION="$VERSION-1"
|
||||
fi
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${TOOLCHAIN}
|
||||
source $HOME/.cargo/env
|
||||
|
||||
rustup target add i686-unknown-linux-gnu
|
||||
rustup target add armv7-unknown-linux-gnueabihf
|
||||
|
||||
cargo install cargo-rpm
|
||||
|
||||
mkdir dist
|
||||
|
||||
cargo build --release
|
||||
cargo rpm build
|
||||
cp target/release/rpmbuild/RPMS/x86_64/vpncloud-${RPM_VERSION}.x86_64.rpm dist/vpncloud_${RPM_VERSION}.x86_64.rpm
|
||||
|
||||
|
||||
build_rpm() {
|
||||
ARCH=$1
|
||||
TARGET=$2
|
||||
if ! [ -f dist/vpncloud_${RPM_VERSION}.${ARCH}.rpm ]; then
|
||||
mkdir -p target
|
||||
[ -L target/assets ] || ln -s ../assets target/assets
|
||||
[ -L target/target ] || ln -s ../target target/target
|
||||
cargo rpm build --target ${TARGET}
|
||||
cp target/${TARGET}/release/rpmbuild/RPMS/${ARCH}/vpncloud-${RPM_VERSION}.${ARCH}.rpm dist/vpncloud_${RPM_VERSION}.${ARCH}.rpm
|
||||
fi
|
||||
}
|
||||
|
||||
build_rpm i686 i686-unknown-linux-gnu
|
|
@ -1,21 +0,0 @@
|
|||
FROM ubuntu:16.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
gcc-arm-linux-gnueabi \
|
||||
libc6-dev-arm64-cross \
|
||||
libc6-dev-armhf-cross \
|
||||
libc6-dev-armel-cross \
|
||||
libc6-dev-i386 \
|
||||
gcc-5-multilib \
|
||||
asciidoctor \
|
||||
musl musl-dev musl-tools \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ADD entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT /entrypoint.sh
|
|
@ -1,5 +0,0 @@
|
|||
name: 'build-static'
|
||||
description: 'Create static binaries'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
TOOLCHAIN=$(grep -e '^toolchain =' Cargo.toml | sed -e 's/toolchain = "\(.*\)"/\1/')
|
||||
UPX_VERSION=$(grep -e '^upx_version =' Cargo.toml | sed -e 's/upx_version = "\(.*\)"/\1/')
|
||||
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
DEB_VERSION=$(echo "$VERSION" | sed -e 's/-/~/g')
|
||||
|
||||
ln -s asm-generic/ /usr/include/asm
|
||||
ln -s /usr/bin/aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-musl-gcc
|
||||
ln -s /usr/bin/arm-linux-gnueabihf-gcc /usr/bin/arm-linux-musleabihf-gcc
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${TOOLCHAIN}
|
||||
source $HOME/.cargo/env
|
||||
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
rustup target add i686-unknown-linux-musl
|
||||
rustup target add armv5te-unknown-linux-musleabi
|
||||
rustup target add armv7-unknown-linux-musleabihf
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
|
||||
curl https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-amd64_linux.tar.xz -Lf | tar -xJ --strip-components=1 -C /usr/bin
|
||||
|
||||
mkdir dist
|
||||
|
||||
build_static() {
|
||||
ARCH=$1
|
||||
TARGET=$2
|
||||
cargo build --release --features installer --target ${TARGET} && upx --lzma target/${TARGET}/release/vpncloud
|
||||
cp target/${TARGET}/release/vpncloud ../dist/vpncloud_${VERSION}_static_${ARCH}
|
||||
}
|
||||
|
||||
build_static amd64 x86_64-unknown-linux-musl
|
||||
#build_static i386 i686-unknown-linux-musl
|
||||
build_static armhf armv7-unknown-linux-musleabihf
|
||||
build_static armel armv5te-unknown-linux-musleabi
|
||||
build_static arm64 aarch64-unknown-linux-musl
|
|
@ -1,11 +0,0 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -1,12 +0,0 @@
|
|||
name: Security audit
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,32 +0,0 @@
|
|||
name: Checks
|
||||
on: [push]
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: szenius/set-timezone@v1.0
|
||||
with:
|
||||
timezoneLinux: Europe/Berlin
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
|
@ -1,14 +1,4 @@
|
|||
target
|
||||
vpncloud-oldnodes
|
||||
._*
|
||||
.~*
|
||||
deb/vpncloud/vpncloud
|
||||
deb/vpncloud/vpncloud.1*
|
||||
Stats.ods
|
||||
dist
|
||||
builder/cache
|
||||
.idea
|
||||
release.sh
|
||||
__pycache__
|
||||
*.pem
|
||||
.vscode
|
||||
deb/vpncloud/vpncloud*
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "libsodium"]
|
||||
path = libsodium
|
||||
url = https://github.com/jedisct1/libsodium
|
|
@ -0,0 +1,48 @@
|
|||
sudo: true
|
||||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
- beta
|
||||
- stable
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
before_script:
|
||||
- ! ' set -e ;
|
||||
pip install ''travis-cargo<0.2'' --user ;
|
||||
export PATH=$HOME/.local/bin:$PATH ;
|
||||
export VERSION=1.0.6 ;
|
||||
wget https://github.com/jedisct1/libsodium/releases/download/$VERSION/libsodium-$VERSION.tar.gz ;
|
||||
tar xvfz libsodium-$VERSION.tar.gz ;
|
||||
pushd libsodium-$VERSION ;
|
||||
./configure --prefix=/usr ;
|
||||
make ;
|
||||
sudo make install ;
|
||||
popd ;
|
||||
'
|
||||
script:
|
||||
- ! ' set -e ;
|
||||
travis-cargo build ;
|
||||
travis-cargo test ;
|
||||
travis-cargo bench ;
|
||||
travis-cargo coverage ;
|
||||
'
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
after_success:
|
||||
- ! ' set -e ;
|
||||
rm -rf target/kcov ;
|
||||
rm target/debug/vpncloud-* ;
|
||||
cargo test ;
|
||||
kcov/build/src/kcov --exclude-pattern=/libsodium/,/x86_64-linux-gnu/,/.cargo --coveralls-id=$TRAVIS_JOB_ID target/kcov target/debug/vpncloud-* ;
|
||||
'
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
env:
|
||||
global:
|
||||
- TRAVIS_CARGO_NIGHTLY_FEATURE=""
|
264
CHANGELOG.md
264
CHANGELOG.md
|
@ -2,268 +2,6 @@
|
|||
|
||||
This project follows [semantic versioning](http://semver.org).
|
||||
|
||||
### UNRELEASED
|
||||
|
||||
- [changed] Changed Rust version to 1.75.0
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fix error when IPv6 is not available
|
||||
|
||||
### v2.3.0 (2021-12-23)
|
||||
|
||||
- [added] Added build for armv5te (thanks to xek)
|
||||
- [added] Option to specify advertised addresses
|
||||
- [added] Peers now learn their own address from peers
|
||||
- [changed] Changed Rust version to 1.57.0
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fixed problem with IPv4 addresses in listen option
|
||||
- [fixed] Fixed periodic broadcast messages in switch mode
|
||||
|
||||
### v2.2.0 (2021-04-06)
|
||||
|
||||
- [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
|
||||
|
||||
### v2.1.0 (2021-02-06)
|
||||
|
||||
- [added] Support for websocket proxy mode
|
||||
- [added] Support for hook scripts to handle certain situations
|
||||
- [added] Support for creating shell completions
|
||||
- [removed] Removed dummy device type
|
||||
- [changed] Updated dependencies
|
||||
- [changed] Changed Rust version to 1.49.0
|
||||
- [fixed] Added missing peer address propagation
|
||||
- [fixed] Fixed problem with peer addresses without port
|
||||
|
||||
### v2.0.1 (2020-11-07)
|
||||
|
||||
- [changed] Changed documentation
|
||||
- [changed] Updated dependencies
|
||||
- [changed] Retrying connections for 120 secs
|
||||
- [changed] Resetting own addresses periodically
|
||||
- [changed] Using smallvec everywhere
|
||||
- [changed] Assume default port for peers without port
|
||||
- [fixed] Fixed corner case with lost init message
|
||||
- [fixed] Do not reconnect to timed out pending connections
|
||||
- [fixed] Most specific claims beat less specific claims
|
||||
- [fixed] Count all invalid protocol traffic
|
||||
- [fixed] Fixed compile with musl
|
||||
- [fixed] Fixed time format in logs
|
||||
|
||||
### v2.0.0 (2020-10-30)
|
||||
|
||||
- [added] **Add strong crypto, complete rewrite of crypto system**
|
||||
- [added] Automatically claim addresses based on interface addresses (disable with --no-auto-claim)
|
||||
- [added] Allow to give --ip instead of ifup cmd
|
||||
- [added] Automatically set optimal MTU on interface
|
||||
- [added] Warning for disabled or loose rp_filter setting
|
||||
- [added] Add --fix-rp-filter to fix rp filter settings
|
||||
- [added] Offer to migrate old configs
|
||||
- [changed] **Complete change of network protocol**
|
||||
- [changed] Negotiate crypto method per peer, select best method
|
||||
- [changed] Make encryption the default, no encryption must be stated explicitly
|
||||
- [changed] Changed default device type to TUN
|
||||
- [changed] Rename subnet to claim
|
||||
- [changed] Set peer exchange interval to 5 minutes
|
||||
- [changed] Periodically send claims with peer list
|
||||
- [changed] Changed Rust version to 1.47.0
|
||||
- [removed] Remove network-id parameter
|
||||
- [removed] Remove port config option in favor of --listen
|
||||
|
||||
### UNRELEASED v1.x.y
|
||||
|
||||
- [added] Added crypto option AES128
|
||||
- [added] Default port for peers
|
||||
- [changed] Updated dependencies
|
||||
- [changed] Removed C code, now 100% Rust
|
||||
- [fixed] Fixed keepalive for small timeouts
|
||||
- [fixed] Fixed problem with port forwarding
|
||||
- [fixed] Fixed problem with TUN on dynamic host addresses
|
||||
|
||||
### v1.4.0 (2020-06-03)
|
||||
|
||||
- [added] Added option to listen on specified IP
|
||||
- [added] Added support for statsd monitoring
|
||||
- [changed] No longer using two sockets for ipv4 and ipv6
|
||||
- [changed] Warning for missing router is now info
|
||||
- [changed] New warning on claimed addresses in learning mode
|
||||
- [changed] Rewrote argument parsing
|
||||
- [changed] Changed stats file format to YAML
|
||||
- [changed] Using asciidoc for manpage
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fixed problem that could lead to 100% cpu consumption
|
||||
- [fixed] Fixed startup race condition
|
||||
|
||||
### v1.3.0 (2020-01-25)
|
||||
|
||||
- [added] Building for aarch64 aka arm64 (thanks to Ivan)
|
||||
- [added] Added feature to disable special NAT support
|
||||
- [changed] Improved port forwarding on quirky routers
|
||||
- [changed] Reduced peer timeout to 5min to work better with NAT
|
||||
- [changed] Improved builder scripts
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fixed problem with growing stats file
|
||||
|
||||
### v1.2.1 (2019-12-22)
|
||||
|
||||
- [fixed] Fixed a problem with service restrictions
|
||||
|
||||
### v1.2.0 (2019-12-20)
|
||||
|
||||
- [added] Added service restrictions to systemd
|
||||
- [changed] Rust version 1.40.0
|
||||
- [changed] Also drop privileges in foreground mode
|
||||
- [changed] Set builders to Ubuntu 16.04 and CentOS 7
|
||||
- [changed] Set keepalive to 120 secs when NAT is detected
|
||||
- [changed] Deleting beacon file at shutdown
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Added parameter keepalive to manpage
|
||||
- [fixed] Fixed problems on stats file when dropping permissions
|
||||
- [fixed] Deleting files before overwriting them
|
||||
- [fixed] Fixed duplicate port bindings
|
||||
|
||||
### v1.1.0 (2019-12-04)
|
||||
|
||||
- [added] Exchange peer timeout and adapt keepalive accordingly
|
||||
- [added] Reducing published peer timeout to 5 min when NAT is detected
|
||||
- [added] Added more tests
|
||||
- [changed] Rust version 1.39.0
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fixed potential startup dependency issue
|
||||
- [fixed] Fixed wrong base62 encoding
|
||||
|
||||
### v1.0.0 (2019-03-21)
|
||||
|
||||
- [added] Added ability to publish small beacons for rendezvous
|
||||
- [added] Added build chain for packages
|
||||
- [added] Added more tests
|
||||
- [changed] Allow to build binary without manpage
|
||||
- [changed] Rust edition 2018
|
||||
- [changed] Rust version 1.33.0
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fixed bug that could cause repeated initialization messages
|
||||
|
||||
### v0.9.1 (2019-02-16)
|
||||
|
||||
- [fixed] Fixed bug in new hex secret key functionality
|
||||
|
||||
### v0.9.0 (2019-02-15)
|
||||
|
||||
- [added] Added support for cross-compilation
|
||||
- [added] Added keepalive option for nodes behind NAT
|
||||
- [added] Added ability to write out statistics file with peers and traffic info
|
||||
- [added] Added dummy device type that does not allocate an interface
|
||||
- [added] Added ability to change /dev/tun path
|
||||
- [changed] Using ring instead of libsodium
|
||||
- [changed] Using PBKDF2 for shared keys (**incompatible**)
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Hashed magics now also consider first character (**incompatible**)
|
||||
|
||||
### v0.8.2 (2019-01-02)
|
||||
|
||||
- [changed] Using serde instead of rustc_serialize
|
||||
- [changed] Updated libsodium to 1.0.16
|
||||
- [changed] Updated dependencies
|
||||
- [changed] Making clippy happy
|
||||
- [fixed] Fixed wrong address
|
||||
|
||||
### v0.8.1 (2017-05-09)
|
||||
|
||||
- [added] Added more tests
|
||||
- [changed] Updated dependencies
|
||||
- [changed] Updated libsodium to 1.0.12
|
||||
- [changed] Small fixes to make clippy happy
|
||||
- [changed] Removed a layer of indirection from inner loop
|
||||
- [fixed] Fixed two problems with routing table
|
||||
|
||||
### v0.8.0 (2016-11-25)
|
||||
|
||||
- [added] Support for automatic port forwarding via UPnP
|
||||
- [added] Added `-s` shorthand for `--subnet`
|
||||
- [added] Support for YAML config file via `--config`
|
||||
- [added] Support for running in the background
|
||||
- [added] Support for dropping permissions
|
||||
- [added] Support for writing a pid file
|
||||
- [added] Support for writing logs to logfile
|
||||
- [changed] Not overriding recently learnt addresses in switch mode
|
||||
- [changed] Caching resolved addresses to increase performance
|
||||
- [changed] Configurable magic header is now used instead of Network-ID (**incompatible**)
|
||||
- [changed] Clarified documentation on TUN netmasks
|
||||
- [changed] Added timestamps to output
|
||||
- [changed] Using new YAML config instead of old config files (**incompatible**)
|
||||
- [changed] Prefer IPv4 over IPv6 when possible
|
||||
- [changed] Updated dependencies
|
||||
- [fixed] Fixed documentation of listen parameter
|
||||
- [fixed] Fixed problem with multiple subnets
|
||||
- [fixed] Fixed problem with interrupted poll after suspend to ram
|
||||
- [fixed] Forgot to extend peer timeout on peer exchange
|
||||
- [fixed] No longer broadcasting to additional addresses
|
||||
|
||||
### v0.7.0 (2016-08-05)
|
||||
|
||||
- [added] Added more tests
|
||||
- [added] Added pluggable polling system
|
||||
- [added] Added documentation
|
||||
- [changed] Code cleanup
|
||||
- [changed] Updated dependencies
|
||||
- [changed] Turned some clippy warnings off
|
||||
- [changed] Cross-compiling for ARM
|
||||
- [changed] Updated libsodium to 1.0.11
|
||||
- [removed] Removed Address remove code for prefix table
|
||||
- [fixed] Reconnecting to lost peers when receiving from them or sending to them
|
||||
- [fixed] Sending peer list more often to prevent timeouts
|
||||
- [fixed] Removing learnt addresses of lost peers
|
||||
- [fixed] Fixed possible crash in message decoding
|
||||
|
||||
### v0.6.0 (2016-06-02)
|
||||
|
||||
- [added] Exponential backoff for reconnect timeouts
|
||||
- [added] Systemd compatible startup scripts
|
||||
- [changed] Repeatedly resolving connect addresses to allow DynDNS
|
||||
- [changed] Listening on IPv4 and IPv6
|
||||
- [changed] Using SO_REUSEADDR to allow frequent rebinding
|
||||
- [changed] Building and using local libsodium library automatically
|
||||
- [changed] Updated dependencies
|
||||
|
||||
### v0.5.0 (2016-04-05)
|
||||
|
||||
- [added] Added license and copyright information
|
||||
- [added] Added documentation for daemon config files
|
||||
- [added] Script for performance measurements
|
||||
- [added] Added more tests and benchmarks
|
||||
- [changed] Daemon now detects network config files on its own
|
||||
- [changed] Using display format for addresses
|
||||
- [changed] Updated dependencies
|
||||
- [changed] New measurements
|
||||
- [changed] Only calling crypto_init once
|
||||
- [changed] Passing listen address as &str
|
||||
- [changed] Using FNV hash for better performance
|
||||
- [changed] Using slice operations instead of loops
|
||||
- [changed] Updated libsodium to 1.0.10
|
||||
- [changed] Renamed default.net to example.net
|
||||
- [fixed] Fixed wrong hex address formatting
|
||||
- [fixed] Fixed peer exchange for more than 65000 peers
|
||||
- [fixed] Initializing crypto for benchmarks
|
||||
- [fixed] Removing learned addresses of lost peers
|
||||
|
||||
### v0.4.3 (2016-02-02)
|
||||
|
||||
- [changed] Updated libsodium to 1.0.8
|
||||
- [fixed] Fixed problem with nodes broadcasting to themselves
|
||||
|
||||
### v0.4.2 (2016-01-19)
|
||||
|
||||
- [changed] Updated dependencies
|
||||
- [changed] New measurements
|
||||
- [changed] Using copy trait more often
|
||||
- [fixed] Fixed deb changelog
|
||||
|
||||
### v0.4.1 (2015-12-22)
|
||||
|
||||
- [changed] Logging more verbosely
|
||||
|
@ -308,7 +46,7 @@ This project follows [semantic versioning](http://semver.org).
|
|||
- [changed] Complete rewrite of encryption code (**incompatible**)
|
||||
- [changed] Removed unused code
|
||||
- [changed] Some speed improvements
|
||||
- [changed] Removed lots of "unsafe" blocks (**fixes security issue**)
|
||||
- [changed] Removed lots of "unsafe" blocks
|
||||
- [changed] Added benchmarks
|
||||
- [changed] Two step handshake in order to fix problems with inconsistent state
|
||||
- [fixed] Pretty error messages instead of panics with traces
|
||||
|
|
File diff suppressed because it is too large
Load Diff
108
Cargo.toml
108
Cargo.toml
|
@ -1,101 +1,31 @@
|
|||
[package]
|
||||
name = "vpncloud"
|
||||
version = "2.4.0"
|
||||
authors = ["Dennis Schwerdel <schwerdel@googlemail.com>"]
|
||||
version = "0.4.1"
|
||||
authors = ["Dennis Schwerdel <schwerdel@informatik.uni-kl.de>"]
|
||||
build = "build.rs"
|
||||
license = "GPL-3.0"
|
||||
description = "Peer-to-peer VPN"
|
||||
homepage = "https://vpncloud.ddswd.de"
|
||||
repository = "https://github.com/dswd/vpncloud"
|
||||
homepage = "https://github.com/dswd/vpncloud.rs"
|
||||
repository = "https://github.com/dswd/vpncloud.rs"
|
||||
keywords = ["vpn", "p2p", "tun", "tap", "network"]
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
toolchain = "1.75.0"
|
||||
upx_version = "4.2.2"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["std", "clock"], default_features = false}
|
||||
structopt = "0.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
signal = "0.7"
|
||||
time = "0.1"
|
||||
docopt = "0.6"
|
||||
rustc-serialize = "0.3"
|
||||
log = "0.3"
|
||||
epoll = "0.2"
|
||||
signal = "0.1"
|
||||
nix = "0.4"
|
||||
libc = "0.2"
|
||||
rand = "0.8"
|
||||
fnv = "1"
|
||||
yaml-rust = "0.4"
|
||||
daemonize = "0.5"
|
||||
ring = "0.17"
|
||||
privdrop = "0.5"
|
||||
byteorder = "1.4"
|
||||
thiserror = "1.0"
|
||||
smallvec = "1.7"
|
||||
dialoguer = { version = "0.11", optional = true }
|
||||
tungstenite = { version = "0.21", optional = true }
|
||||
url = { version = "2.2", optional = true }
|
||||
igd = { version = "0.12", optional = true }
|
||||
aligned_alloc = "0.1.1"
|
||||
rand = "0.3"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
iai = "0.1"
|
||||
[build-dependencies]
|
||||
gcc = "0.3"
|
||||
pkg-config = "0.3.6"
|
||||
|
||||
[features]
|
||||
default = ["nat", "websocket", "wizard"]
|
||||
nat = ["igd"]
|
||||
websocket = ["tungstenite", "url"]
|
||||
wizard = ["dialoguer"]
|
||||
installer = []
|
||||
|
||||
[[bench]]
|
||||
name = "criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "valgrind"
|
||||
harness = false
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
|
||||
[profile.dev]
|
||||
lto = false
|
||||
|
||||
[profile.test]
|
||||
lto = false
|
||||
|
||||
[package.metadata.deb]
|
||||
extended-description = """\
|
||||
VpnCloud is a high performance peer-to-peer mesh VPN over UDP supporting strong encryption,
|
||||
NAT traversal and a simple configuration. It establishes a fully-meshed self-healing VPN
|
||||
network in a peer-to-peer manner with strong end-to-end encryption based on elliptic curve
|
||||
keys and AES-256. VpnCloud creates a virtual network interface on the host and forwards all
|
||||
received data via UDP to the destination. It can work on TUN devices (IP based) and TAP
|
||||
devices (Ethernet based)."""
|
||||
license-file = ["LICENSE.md", "1"]
|
||||
changelog = "assets/changelog.txt"
|
||||
section = "net"
|
||||
depends = "libc6 (>= 2.23), libgcc1 (>= 1:6.0.1)"
|
||||
maintainer-scripts = "assets/deb-scripts"
|
||||
assets = [
|
||||
["target/release/vpncloud", "/usr/bin/vpncloud", "755"],
|
||||
["assets/example.net.disabled", "/etc/vpncloud/example.net.disabled", "600"],
|
||||
["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"],
|
||||
["target/vpncloud.1.gz", "/usr/share/man/man1/vpncloud.1.gz", "644"]
|
||||
]
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [
|
||||
{ source = "target/release/vpncloud", dest = "/usr/bin/vpncloud", mode = "755" },
|
||||
{ source = "assets/example.net.disabled", dest = "/etc/vpncloud/example.net.disabled", mode = "600" },
|
||||
{ source = "assets/vpncloud@.service", dest = "/lib/systemd/system/vpncloud@.service", mode = "644" },
|
||||
{ source = "assets/vpncloud.target", dest = "/lib/systemd/system/vpncloud.target", mode = "644" },
|
||||
{ source = "assets/vpncloud-wsproxy.service", dest = "/lib/systemd/system/vpncloud-wsproxy.service", mode = "644" },
|
||||
{ source = "target/vpncloud.1.gz", dest = "/usr/share/man/man1/vpncloud.1.gz", mode = "644" }
|
||||
]
|
||||
auto-req = "no"
|
||||
default = []
|
||||
bench = []
|
||||
|
|
682
LICENSE.md
682
LICENSE.md
|
@ -1,682 +0,0 @@
|
|||
# License: GPL-3
|
||||
|
||||
VpnCloud - Peer-to-Peer VPN
|
||||
Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Full license text follows.
|
||||
|
||||
### Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom
|
||||
to share and change all versions of a program--to make sure it remains
|
||||
free software for all its users. We, the Free Software Foundation, use
|
||||
the GNU General Public License for most of our software; it applies
|
||||
also to any other work released this way by its authors. You can apply
|
||||
it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you
|
||||
have certain responsibilities if you distribute copies of the
|
||||
software, or if you modify it: responsibilities to respect the freedom
|
||||
of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the
|
||||
manufacturer can do so. This is fundamentally incompatible with the
|
||||
aim of protecting users' freedom to change the software. The
|
||||
systematic pattern of such abuse occurs in the area of products for
|
||||
individuals to use, which is precisely where it is most unacceptable.
|
||||
Therefore, we have designed this version of the GPL to prohibit the
|
||||
practice for those products. If such problems arise substantially in
|
||||
other domains, we stand ready to extend this provision to those
|
||||
domains in future versions of the GPL, as needed to protect the
|
||||
freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish
|
||||
to avoid the special danger that patents applied to a free program
|
||||
could make it effectively proprietary. To prevent this, the GPL
|
||||
assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in
|
||||
detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU General Public
|
||||
License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that numbered version or
|
||||
of any later version published by the Free Software Foundation. If the
|
||||
Program does not specify a version number of the GNU General Public
|
||||
License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU General Public License can be used, that proxy's public
|
||||
statement of acceptance of a version permanently authorizes you to
|
||||
choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
### How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these
|
||||
terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to
|
||||
attach them to the start of each source file to most effectively state
|
||||
the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper
|
||||
mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands \`show w' and \`show c' should show the
|
||||
appropriate parts of the General Public License. Of course, your
|
||||
program's commands might be different; for a GUI interface, you would
|
||||
use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. For more information on this, and how to apply and follow
|
||||
the GNU GPL, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your
|
||||
program into proprietary programs. If your program is a subroutine
|
||||
library, you may consider it more useful to permit linking proprietary
|
||||
applications with the library. If this is what you want to do, use the
|
||||
GNU Lesser General Public License instead of this License. But first,
|
||||
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
|
91
README.md
91
README.md
|
@ -1,76 +1,47 @@
|
|||
VpnCloud - Peer-to-Peer VPN
|
||||
---------------------------
|
||||
![Checks](https://github.com/dswd/vpncloud/workflows/Checks/badge.svg?branch=master)
|
||||
![Security audit](https://github.com/dswd/vpncloud/workflows/Security%20audit/badge.svg?branch=master)
|
||||
|
||||
**VpnCloud** is a high performance peer-to-peer mesh VPN over UDP supporting strong encryption, NAT traversal and a simple configuration. It establishes a fully-meshed self-healing VPN network in a peer-to-peer manner with strong end-to-end encryption based on elliptic curve keys and AES-256. VpnCloud creates a virtual network interface on the host and forwards all received data via UDP to the destination. It can work on TUN devices (IP based) and TAP devices (Ethernet based).
|
||||
[![Build Status](https://travis-ci.org/dswd/vpncloud.rs.svg?branch=master)](https://travis-ci.org/dswd/vpncloud.rs)
|
||||
[![Coverage Status](https://coveralls.io/repos/dswd/vpncloud.rs/badge.svg?branch=master&service=github)](https://coveralls.io/github/dswd/vpncloud.rs?branch=master)
|
||||
[![Latest Version](https://img.shields.io/crates/v/vpncloud.svg)](https://crates.io/crates/vpncloud)
|
||||
|
||||
```sh
|
||||
$> vpncloud -c REMOTE_HOST:PORT -p 'mypassword' --ip 10.0.0.1/24
|
||||
**VpnCloud** is a simple VPN over UDP. It creates a virtual network interface on
|
||||
the host and forwards all received data via UDP to the destination. It can work
|
||||
on TUN devices (IP based) and TAP devices (Ethernet based). Tunneling traffic
|
||||
between two nodes can be as easy as:
|
||||
|
||||
```
|
||||
vpncloud -c REMOTE_HOST:PORT --ifup 'ifconfig $IFNAME 10.0.0.1/24 mtu 1400 up'
|
||||
```
|
||||
|
||||
or as config file:
|
||||
|
||||
```yaml
|
||||
crypto:
|
||||
password: mysecret
|
||||
ip: 10.0.0.1
|
||||
peers:
|
||||
- REMOTE_HOST:PORT
|
||||
```
|
||||
|
||||
For more information, please see the [Website](https://vpncloud.ddswd.de) or the [Discussions group](https://github.com/dswd/vpncloud/discussions).
|
||||
More details can be found in [the manpage](vpncloud.md).
|
||||
Some performance measurements can be found [here](performance.md).
|
||||
|
||||
|
||||
### Project Status
|
||||
This project is still [under development](CHANGELOG.md) but has reached a
|
||||
somewhat stable state. VpnCloud features the following functionality:
|
||||
### Current Status
|
||||
|
||||
* Automatic peer-to-peer meshing, no central servers
|
||||
This project is still [under development](CHANGELOG.md) and has yet to reach a stable state.
|
||||
However, the main functionality should work and you are invited to test it.
|
||||
This is what works:
|
||||
|
||||
* Setting up tunnels between two networks via Ethernet (TAP) and IP (TUN)
|
||||
* Connecting multiple networks with multiple forwarding behaviors (Hub, Switch, Router)
|
||||
* Encrypted connections using [libsodium](https://github.com/jedisct1/libsodium)
|
||||
* Automatic peer-to-peer meshing
|
||||
* NAT and (limited) firewall traversal using hole punching
|
||||
* Automatic reconnecting when connections are lost
|
||||
* Connecting hundreds of nodes with the VPN
|
||||
* High throughput and low additional latency (see [performance page](https://vpncloud.ddswd.de/features/performance))
|
||||
* Creating virtual network interfaces based on Ethernet (TAP) and IP (TUN)
|
||||
* Strong end-to-end encryption using Curve25519 key pairs and AES methods
|
||||
* Support for different forwarding/routing behaviors (Hub, Switch, Router)
|
||||
* NAT and firewall traversal using hole punching
|
||||
* Automatic port forwarding via UPnP
|
||||
* Websocket proxy mode for restrictive environments
|
||||
* Support for tunneled VLans (TAP devices)
|
||||
* Support for publishing [beacons](https://vpncloud.ddswd.de/docs/beacons) to help nodes find each others
|
||||
* Support for statsd monitoring
|
||||
* Low memory footprint
|
||||
* Single binary, no dependencies, no kernel module
|
||||
* Non-native forwarding modes, e.g. IP based learning switch and prefix routed Ethernet networks.
|
||||
* High throughput and low additional latency (see [performance page](performance.md))
|
||||
* Support for tunneled VLans (TAP device)
|
||||
|
||||
### Installing
|
||||
However there are some open issues:
|
||||
|
||||
#### Compiling from source
|
||||
Prerequisites: Git, [Cargo](https://www.rust-lang.org/install.html), asciidoctor
|
||||
* Encryption has not been thoroughly reviewed, use with care.
|
||||
* The software is not very well tested and the protocol can change.
|
||||
|
||||
The checked-out code can be compiled with ``cargo build`` or ``cargo build --release`` (release version). The binary could then be found in `target/release/vpncloud`.
|
||||
|
||||
The tests can be run via ``cargo test``.
|
||||
|
||||
|
||||
#### Cross-Compiling & packaging
|
||||
Please see the [builder folder](builder).
|
||||
|
||||
|
||||
### Contributions welcome
|
||||
There are several areas in which still some work has to be done and where
|
||||
contributions are very welcome:
|
||||
|
||||
* **Linux packages**: VpnCloud is stable enough to be packaged for Linux
|
||||
distributions. Maintainers who want to package VpnCloud are very welcome.
|
||||
* **Help with other platforms**: If you are a Rust developer with experience
|
||||
on Windows or MacOS your help on porting VpnCloud to those platforms is very
|
||||
welcome.
|
||||
* **Security review**: The security has been implemented with strong security
|
||||
primitives but it would be great if a cryptography expert could verify the
|
||||
system.
|
||||
* **Feedback on use cases**: Some feedback on how VpnCloud is being used and
|
||||
maybe some tutorials covering common use cases would be nice.
|
||||
Please feel free to help and contribute code.
|
||||
|
||||
|
||||
### Semantic Versioning
|
||||
This project uses [semantic versioning](http://semver.org).
|
||||
|
||||
This project uses [semantic versioning](http://semver.org). Currently that means that everything can change between versions before 1.0 is finally released.
|
||||
|
|
|
@ -1,335 +0,0 @@
|
|||
vpncloud (2.3.0) stable; urgency=medium
|
||||
|
||||
* [added] Added build for armv5te (thanks to xek)
|
||||
* [added] Option to specify advertised addresses
|
||||
* [added] Peers now learn their own address from peers
|
||||
* [changed] Changed Rust version to 1.57.0
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Fixed problem with IPv4 addresses in listen option
|
||||
* [fixed] Fixed periodic broadcast messages in switch mode
|
||||
|
||||
-- Dennis Schwerdel <schwerdel+vpncloud@googlemail.com> Thu, 23 Dec 2021 20:41:00 +0100
|
||||
|
||||
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
|
||||
|
||||
* [added] Support for websocket proxy mode
|
||||
* [added] Support for hook scripts to handle certain situations
|
||||
* [added] Support for creating shell completions
|
||||
* [removed] Removed dummy device type
|
||||
* [changed] Updated dependencies
|
||||
* [changed] Changed Rust version to 1.49.0
|
||||
* [fixed] Added missing peer address propagation
|
||||
* [fixed] Fixed problem with peer addresses without port
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 06 Feb 2021 13:13:00 +0100
|
||||
|
||||
vpncloud (2.0.1) stable; urgency=medium
|
||||
|
||||
* [changed] Changed documentation
|
||||
* [changed] Updated dependencies
|
||||
* [changed] Retrying connections for 120 secs
|
||||
* [changed] Resetting own addresses periodically
|
||||
* [changed] Using smallvec everywhere
|
||||
* [changed] Assume default port for peers without port
|
||||
* [fixed] Fixed corner case with lost init message
|
||||
* [fixed] Do not reconnect to timed out pending connections
|
||||
* [fixed] Most specific claims beat less specific claims
|
||||
* [fixed] Count all invalid protocol traffic
|
||||
* [fixed] Fixed compile with musl
|
||||
* [fixed] Fixed time format in logs
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 07 Nov 2020 12:28:00 +0100
|
||||
|
||||
vpncloud (2.0.0) stable; urgency=medium
|
||||
|
||||
* [added] **Add strong crypto, complete rewrite of crypto system**
|
||||
* [added] Automatically claim addresses based on interface addresses (disable with --no-auto-claim)
|
||||
* [added] Allow to give --ip instead of ifup cmd
|
||||
* [added] Automatically set optimal MTU on interface
|
||||
* [added] Warning for disabled or loose rp_filter setting
|
||||
* [added] Add --fix-rp-filter to fix rp filter settings
|
||||
* [added] Offer to migrate old configs
|
||||
* [changed] **Complete change of network protocol**
|
||||
* [changed] Negotiate crypto method per peer, select best method
|
||||
* [changed] Make encryption the default, no encryption must be stated explicitly
|
||||
* [changed] Changed default device type to TUN
|
||||
* [changed] Rename subnet to claim
|
||||
* [changed] Set peer exchange interval to 5 minutes
|
||||
* [changed] Periodically send claims with peer list
|
||||
* [changed] Changed Rust version to 1.47.0
|
||||
* [removed] Remove network-id parameter
|
||||
* [removed] Remove port config option in favor of --listen
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Fri, 30 Oct 2020 22:07:00 +0100
|
||||
|
||||
vpncloud (1.4.0) stable; urgency=medium
|
||||
|
||||
* [added] Added option to listen on specified IP
|
||||
* [added] Added support for statsd monitoring
|
||||
* [changed] No longer using two sockets for ipv4 and ipv6
|
||||
* [changed] Warning for missing router is now info
|
||||
* [changed] New warning on claimed addresses in learning mode
|
||||
* [changed] Rewrote argument parsing
|
||||
* [changed] Changed stats file format to YAML
|
||||
* [changed] Using asciidoc for manpage
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Fixed problem that could lead to 100% cpu consumption
|
||||
* [fixed] Fixed startup race condition
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Wed, 03 Jun 2020 17:46:00 +0200
|
||||
|
||||
vpncloud (1.3.0) stable; urgency=medium
|
||||
|
||||
* [added] Building for aarch64 aka arm64 (thanks to Ivan)
|
||||
* [added] Added feature to disable special NAT support
|
||||
* [changed] Improved port forwarding on quirky routers
|
||||
* [changed] Reduced peer timeout to 5min to work better with NAT
|
||||
* [changed] Improved builder scripts
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Fixed problem with growing stats file
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 25 Jan 2020 13:49:34 +0100
|
||||
|
||||
vpncloud (1.2.1) stable; urgency=medium
|
||||
|
||||
* [fixed] Fixed a problem with service restrictions
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Sun, 22 Dec 2019 16:47:38 +0100
|
||||
|
||||
vpncloud (1.2.0) stable; urgency=medium
|
||||
|
||||
* [added] Added service restrictions to systemd
|
||||
* [changed] Rust version 1.40.0
|
||||
* [changed] Also drop privileges in foreground mode
|
||||
* [changed] Set builders to Ubuntu 16.04 and CentOS 7
|
||||
* [changed] Set keepalive to 120 secs when NAT is detected
|
||||
* [changed] Deleting beacon file at shutdown
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Added parameter keepalive to manpage
|
||||
* [fixed] Fixed problems on stats file when dropping permissions
|
||||
* [fixed] Deleting files before overwriting them
|
||||
* [fixed] Fixed duplicate port bindings
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Fri, 20 Dec 2019 16:31:07 +0100
|
||||
|
||||
vpncloud (1.1.0) stable; urgency=medium
|
||||
|
||||
* [added] Exchange peer timeout and adapt keepalive accordingly
|
||||
* [added] Reducing published peer timeout to 5 min when NAT is detected
|
||||
* [added] Added more tests
|
||||
* [changed] Rust version 1.41.0
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Fixed potential startup dependency issue
|
||||
* [fixed] Fixed wrong base62 encoding
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Thu, 04 Dec 2019 19:01:34 +0100
|
||||
|
||||
vpncloud (1.0.0) stable; urgency=medium
|
||||
|
||||
* [added] Added ability to publish small beacons for rendezvous
|
||||
* [added] Added build chain for packages
|
||||
* [added] Added more tests
|
||||
* [changed] Allow to build binary without manpage
|
||||
* [changed] Rust edition 2018
|
||||
* [changed] Rust version 1.33.0
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Fixed bug that could cause repeated initialization messages
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Thu, 21 Mar 2019 18:06:19 +0100
|
||||
|
||||
vpncloud (0.9.1) stable; urgency=medium
|
||||
|
||||
* [fixed] Fixed bug in new hex secret key functionality
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Sat, 16 Jan 2019 15:21:32 +0100
|
||||
|
||||
vpncloud (0.9.0) stable; urgency=medium
|
||||
|
||||
* [added] Added support for cross-compilation
|
||||
* [added] Added keepalive option for nodes behind NAT
|
||||
* [added] Added ability to write out statistics file with peers and traffic info
|
||||
* [added] Added dummy device type that does not allocate an interface
|
||||
* [added] Added ability to change /dev/tun path
|
||||
* [changed] Using ring instead of libsodium
|
||||
* [changed] Using PBKDF2 for shared keys (**incompatible**)
|
||||
* [changed] Updated dependencies
|
||||
* [fixed] Hashed magics now also consider first character (**incompatible**)
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Fri, 15 Jan 2019 23:44:54 +0100
|
||||
|
||||
vpncloud (0.8.2) stable; urgency=medium
|
||||
|
||||
* [changed] Using serde instead of rustc_serialize
|
||||
* [changed] Updated libsodium to 1.0.16
|
||||
* [changed] Updated dependencies
|
||||
* [changed] Making clippy happy
|
||||
* [fixed] Fixed wrong address
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@googlemail.com> Wed, 02 Jan 2019 19:09:20 +0100
|
||||
|
||||
vpncloud (0.8.1) stable; urgency=medium
|
||||
|
||||
* [added] Added more tests
|
||||
* [changed] Updated dependencies
|
||||
* [changed] Updated libsodium to 1.0.12
|
||||
* [changed] Small fixes to make clippy happy
|
||||
* [changed] Removed a layer of indirection from inner loop
|
||||
* [fixed] Fixed two problems with routing table
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 09 May 2017 09:16:31 +0200
|
||||
|
||||
vpncloud (0.8.0) stable; urgency=medium
|
||||
|
||||
* [added] Support for automatic port forwarding via UPnP
|
||||
* [added] Added `-s` shorthand for `--subnet`
|
||||
* [added] Support for YAML config file via `--config`
|
||||
* [added] Support for running in the background
|
||||
* [added] Support for dropping permissions
|
||||
* [added] Support for writing a pid file
|
||||
* [added] Support for writing logs to logfile
|
||||
* [changed] Not overriding recently learnt addresses in switch mode
|
||||
* [changed] Caching resolved addresses to increase performance
|
||||
* [changed] Configurable magic header is now used instead of Network-ID (**incompatible**)
|
||||
* [changed] Clarified documentation on TUN netmasks
|
||||
* [changed] Added timestamps to output
|
||||
* [changed] Using new YAML config instead of old config files (**incompatible**)
|
||||
* [changed] Prefer IPv4 over IPv6 when possible
|
||||
* [fixed] Fixed documentation of listen parameter
|
||||
* [fixed] Fixed problem with multiple subnets
|
||||
* [fixed] Fixed problem with interrupted poll after suspend to ram
|
||||
* [fixed] Forgot to extend peer timeout on peer exchange
|
||||
* [fixed] No longer broadcasting to additional addresses
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Fri, 25 Nov 2016 07:30:17 +0100
|
||||
|
||||
vpncloud (0.7.0) stable; urgency=medium
|
||||
|
||||
* [added] Added more tests
|
||||
* [added] Added pluggable polling system
|
||||
* [added] Added documentation
|
||||
* [changed] Code cleanup
|
||||
* [changed] Updated dependencies
|
||||
* [changed] Turned some clippy warnings off
|
||||
* [changed] Cross-compiling for ARM
|
||||
* [changed] Updated libsodium to 1.0.11
|
||||
* [removed] Removed Address remove code for prefix table
|
||||
* [fixed] Reconnecting to lost peers when receiving from them or sending to them
|
||||
* [fixed] Sending peer list more often to prevent timeouts
|
||||
* [fixed] Removing learnt addresses of lost peers
|
||||
* [fixed] Fixed possible crash in message decoding
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Fri, 05 Aug 2016 08:47:06 +0200
|
||||
|
||||
vpncloud (0.6.0) stable; urgency=medium
|
||||
|
||||
* [added] Exponential backoff for reconnect timeouts
|
||||
* [added] Systemd compatible startup scripts
|
||||
* [changed] Repeatedly resolving connect addresses to allow DynDNS
|
||||
* [changed] Listening on IPv4 and IPv6
|
||||
* [changed] Using SO_REUSEADDR to allow frequent rebinding
|
||||
* [changed] Building and using local libsodium library automatically
|
||||
* [changed] Updated dependencies
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Thu, 02 Jun 2016 09:33:01 +0200
|
||||
|
||||
vpncloud (0.5.0) stable; urgency=medium
|
||||
|
||||
* [added] Added license and copyright information
|
||||
* [added] Added documentation for daemon config files
|
||||
* [added] Script for performance measurements
|
||||
* [added] Added more tests and benchmarks
|
||||
* [changed] Daemon now detects network config files on its own
|
||||
* [changed] Using display format for addresses
|
||||
* [changed] Updated dependencies
|
||||
* [changed] New measurements
|
||||
* [changed] Only calling crypto_init once
|
||||
* [changed] Passing listen address as &str
|
||||
* [changed] Using FNV hash for better performance
|
||||
* [changed] Using slice operations instead of loops
|
||||
* [changed] Updated libsodium to 1.0.10
|
||||
* [changed] Renamed default.net to example.net
|
||||
* [fixed] Fixed wrong hex address formatting
|
||||
* [fixed] Fixed peer exchange for more than 65000 peers
|
||||
* [fixed] Initializing crypto for benchmarks
|
||||
* [fixed] Removing learned addresses of lost peers
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 05 Apr 2016 15:33:20 +0200
|
||||
|
||||
vpncloud (0.4.3) stable; urgency=medium
|
||||
|
||||
* [changed] Updated libsodium to 1.0.8
|
||||
* [fixed] Fixed problem with nodes broadcasting to themselves
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 02 Feb 2016 11:27:04 +0100
|
||||
|
||||
vpncloud (0.4.2) stable; urgency=medium
|
||||
|
||||
* [changed] Updated dependencies
|
||||
* [changed] New measurements
|
||||
* [changed] Using copy trait more often
|
||||
* [fixed] Fixed deb changelog
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 19 Jan 2016 21:56:12 +0100
|
||||
|
||||
vpncloud (0.4.1) stable; urgency=medium
|
||||
|
||||
* [changed] Logging more verbosely
|
||||
* [fixed] Removing NULL-bytes from interface name
|
||||
* [fixed] Supporting hostnames as peers
|
||||
* [fixed] No longer encrypting multiple times
|
||||
* [fixed] Properly decoding protocol header when sending
|
||||
* [fixed] Corrected size of read data
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 22 Dec 2015 22:51:30 +0100
|
||||
|
||||
vpncloud (0.4) stable; urgency=medium
|
||||
|
||||
* [added] Init script
|
||||
* [changed] Removed last payload memcopy
|
||||
* [changed] Using RNG to select peers for peers list exchange
|
||||
* [changed] Updated dependency versions
|
||||
* [changed] Updated documentation
|
||||
* [fixed] Printing errors instead of panics in some cases
|
||||
* [fixed] Build script for Debian packages
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 22 Dec 2015 19:23:26 +0100
|
||||
|
||||
vpncloud (0.3.1) stable; urgency=medium
|
||||
|
||||
* Preventing nodes from connecting to themselves
|
||||
* Flushing TAP/TUN device after writing to it
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Thu, 03 Dec 2015 21:53:43 +0100
|
||||
|
||||
vpncloud (0.3.0) stable; urgency=medium
|
||||
|
||||
* Inluding libsodium-1.0.7
|
||||
* Support for AES256
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 01 Dec 2015 16:12:16 +0100
|
||||
|
||||
vpncloud (0.2.0) stable; urgency=medium
|
||||
|
||||
* More stable release
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Thu, 26 Nov 2015 17:41:40 +0100
|
||||
|
||||
vpncloud (0.1.0) stable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 24 Nov 2015 09:31:47 +0100
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
systemctl daemon-reload
|
||||
chmod 700 /etc/vpncloud
|
|
@ -1,82 +0,0 @@
|
|||
# This configuration file uses the YAML format.
|
||||
# ~ means "no value" (i.e. "default value")
|
||||
# Replace it by a value and put quotes (") around values with special characters
|
||||
# List items start with a dash and a space (- )
|
||||
# Note that the whitespace before the settings names is important for the file structure
|
||||
|
||||
|
||||
listen: 3210 # The port number or ip:port on which to listen for data.
|
||||
|
||||
peers: # Address of a peer to connect to.
|
||||
# The address should be in the form `addr:port`.
|
||||
# Put [] for an empty list
|
||||
- node2.example.com:3210
|
||||
- node3.example.com:3210
|
||||
|
||||
crypto: # Crypto settings
|
||||
password: ~ # <-- CHANGE # A password to encrypt the VPN data.
|
||||
private-key: ~ # Private key (alternative to password)
|
||||
public-key: ~ # Public key (alternative to password)
|
||||
trusted-keys: [] # Trusted keys (alternative to password)
|
||||
# Replace [] with list of keys
|
||||
|
||||
ip: ~ # <-- CHANGE # An IP address to set on the device, e.g. 10.0.0.1
|
||||
# Must be different for every node on the VPN
|
||||
|
||||
|
||||
# ------------------ Advanced features ahead --------------------
|
||||
|
||||
auto-claim: true # Whether to automatically claim the configured IP on tun devices
|
||||
|
||||
claims: # The local subnets to use. This parameter should be in the form
|
||||
# `address/prefixlen` where address is an IPv4 address, an IPv6 address, or a
|
||||
# MAC address. The prefix length is the number of significant front bits that
|
||||
# distinguish the subnet from other subnets.
|
||||
# - 10.1.1.0/24
|
||||
|
||||
ifup: ~ # Command to setup the interface. Use $IFNAME for interface name.
|
||||
ifdown: ~ # Command to tear down the interface. Use $IFNAME for interface name.
|
||||
|
||||
device: # Device settings
|
||||
name: "vpncloud%d" # Name of the virtual device. Any `%d` will be filled with a free number.
|
||||
type: tun # Set the type of network. There are two options: **tap** devices process
|
||||
# Ethernet frames **tun** devices process IP packets. [default: `tun`]
|
||||
path: "/dev/net/tun" # Path of the tun device
|
||||
fix-rp-filter: false # Whether to fix detected rp-filter problems
|
||||
|
||||
mode: normal # Mode to run in, "normal", "hub", "switch", or "router" (see manpage)
|
||||
|
||||
port-forwarding: true # Try to map a port on the router
|
||||
|
||||
switch-timeout: 300 # Switch timeout in seconds (switch mode only)
|
||||
|
||||
peer-timeout: 300 # Peer timeout in seconds
|
||||
keepalive: ~ # Keepalive interval in seconds
|
||||
|
||||
beacon: # Beacon settings
|
||||
store: ~ # File or command (prefix: "|") to use for storing beacons
|
||||
load: ~ # File or command (prefix: "|") to use for loading beacons
|
||||
interval: 3600 # How often to load and store beacons (in seconds)
|
||||
password: ~ # Password to encrypt beacon data with
|
||||
|
||||
statsd: # Statsd settings
|
||||
server: ~ # Statsd server name:port
|
||||
prefix: ~ # Prefix to use for stats keys
|
||||
|
||||
pid-file: ~ # Store the process id in this file when running in the background
|
||||
stats-file: ~ # Periodically write statistics on peers and current traffic to the given file
|
||||
|
||||
hook: ~ # Hook script to run for every event
|
||||
hooks: {} # Multiple hook scripts to run for specific events
|
||||
|
||||
|
||||
|
||||
# Copy this template and save it to a file named /etc/vpncloud/MYNET.net (replace MYNET with your network name)
|
||||
#
|
||||
# On systems using systemd (most common):
|
||||
# start/stop the network: service vpncloud@MYNET start/stop
|
||||
# enable/disable automatic startup: systemctl enable/disable vpncloud@MYNET
|
||||
#
|
||||
# On older systems (using sysv init):
|
||||
# Add the network name to /etc/default/vpncloud
|
||||
# start/stop all VpnCloud networks: /etc/init.d/vpncloud start/stop
|
|
@ -1,22 +0,0 @@
|
|||
[Unit]
|
||||
Description=VpnCloud websocket proxy
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Documentation=man:vpncloud(1)
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/vpncloud ws-proxy -l 3210
|
||||
RestartSec=5s
|
||||
Restart=on-failure
|
||||
TasksMax=10
|
||||
MemoryMax=50M
|
||||
PrivateTmp=yes
|
||||
ProtectHome=yes
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/log /run
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SYS_CHROOT
|
||||
DeviceAllow=/dev/null rw
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,2 +0,0 @@
|
|||
[Unit]
|
||||
Description=VpnCloud target allowing to start/stop all vpncloud@.service instances at once
|
|
@ -1,26 +0,0 @@
|
|||
[Unit]
|
||||
Description=VpnCloud network '%I'
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
PartOf=vpncloud.target
|
||||
Documentation=man:vpncloud(1)
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
ExecStart=/usr/bin/vpncloud --config /etc/vpncloud/%i.net --log-file /var/log/vpncloud-%i.log --stats-file /var/log/vpncloud-%i.stats --daemon --pid-file /run/vpncloud-%i.pid
|
||||
PIDFile=/run/vpncloud-%i.pid
|
||||
WorkingDirectory=/etc/vpncloud
|
||||
RestartSec=5s
|
||||
Restart=on-failure
|
||||
TasksMax=10
|
||||
MemoryMax=50M
|
||||
PrivateTmp=yes
|
||||
ProtectHome=yes
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/log /run
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT
|
||||
DeviceAllow=/dev/null rw
|
||||
DeviceAllow=/dev/net/tun rw
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,77 +0,0 @@
|
|||
#[macro_use]
|
||||
mod util {
|
||||
include!("../src/util.rs");
|
||||
}
|
||||
mod error {
|
||||
include!("../src/error.rs");
|
||||
}
|
||||
mod payload {
|
||||
include!("../src/payload.rs");
|
||||
}
|
||||
mod types {
|
||||
include!("../src/types.rs");
|
||||
}
|
||||
mod table {
|
||||
include!("../src/table.rs");
|
||||
}
|
||||
mod cloud {
|
||||
include!("../src/cloud.rs");
|
||||
}
|
||||
mod config {
|
||||
include!("../src/config.rs");
|
||||
}
|
||||
mod device {
|
||||
include!("../src/device.rs");
|
||||
}
|
||||
mod net {
|
||||
include!("../src/net.rs");
|
||||
}
|
||||
mod beacon {
|
||||
include!("../src/beacon.rs");
|
||||
}
|
||||
mod messages {
|
||||
include!("../src/messages.rs");
|
||||
}
|
||||
mod port_forwarding {
|
||||
include!("../src/port_forwarding.rs");
|
||||
}
|
||||
mod traffic {
|
||||
include!("../src/traffic.rs");
|
||||
}
|
||||
mod poll {
|
||||
pub mod epoll{
|
||||
include!("../src/poll/epoll.rs");
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub use self::epoll::EpollWait as WaitImpl;
|
||||
|
||||
use std::io;
|
||||
|
||||
pub enum WaitResult {
|
||||
Timeout,
|
||||
Socket,
|
||||
Device,
|
||||
Error(io::Error)
|
||||
}
|
||||
}
|
||||
mod crypto {
|
||||
pub mod core {
|
||||
include!("../src/crypto/core.rs");
|
||||
}
|
||||
pub mod init {
|
||||
include!("../src/crypto/init.rs");
|
||||
}
|
||||
pub mod rotate {
|
||||
include!("../src/crypto/rotate.rs");
|
||||
}
|
||||
pub mod common {
|
||||
include!("../src/crypto/common.rs");
|
||||
}
|
||||
pub use common::*;
|
||||
pub use self::core::{EXTRA_LEN, TAG_LEN};
|
||||
}
|
||||
mod tests {
|
||||
pub mod common {
|
||||
include!("../src/tests/common.rs");
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
#![allow(dead_code, unused_macros, unused_imports)]
|
||||
#[macro_use] extern crate serde;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
||||
|
||||
use smallvec::smallvec;
|
||||
use ring::aead;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket};
|
||||
|
||||
include!(".code.rs");
|
||||
|
||||
pub use error::Error;
|
||||
use util::{MockTimeSource, MsgBuffer};
|
||||
use types::{Address, Range};
|
||||
use table::ClaimTable;
|
||||
use device::Type;
|
||||
use config::Config;
|
||||
use payload::{Packet, Frame, Protocol};
|
||||
use crypto::core::{create_dummy_pair, EXTRA_LEN};
|
||||
use tests::common::{TunSimulator, TapSimulator};
|
||||
|
||||
fn udp_send(c: &mut Criterion) {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let data = [0; 1400];
|
||||
let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1);
|
||||
let mut g = c.benchmark_group("udp_send");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("udp_send", |b| {
|
||||
b.iter(|| sock.send_to(&data, addr).unwrap());
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn decode_ipv4(c: &mut Criterion) {
|
||||
let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2];
|
||||
let mut g = c.benchmark_group("payload");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("decode_ipv4", |b| {
|
||||
b.iter(|| Packet::parse(&data).unwrap());
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn decode_ipv6(c: &mut Criterion) {
|
||||
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,
|
||||
4, 3, 2, 1
|
||||
];
|
||||
let mut g = c.benchmark_group("payload");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("decode_ipv6", |b| {
|
||||
b.iter(|| Packet::parse(&data).unwrap());
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn decode_ethernet(c: &mut Criterion) {
|
||||
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let mut g = c.benchmark_group("payload");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("decode_ethernet", |b| {
|
||||
b.iter(|| Frame::parse(&data).unwrap());
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn decode_ethernet_with_vlan(c: &mut Criterion) {
|
||||
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0, 4, 210, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let mut g = c.benchmark_group("payload");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("decode_ethernet_with_vlan", |b| {
|
||||
b.iter(|| Frame::parse(&data).unwrap());
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn lookup_warm(c: &mut Criterion) {
|
||||
let mut table = ClaimTable::<MockTimeSource>::new(60, 60);
|
||||
let addr = Address::from_str("1.2.3.4").unwrap();
|
||||
table.cache(addr, SocketAddr::from_str("1.2.3.4:3210").unwrap());
|
||||
let mut g = c.benchmark_group("table");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("lookup_warm", |b| {
|
||||
b.iter(|| table.lookup(addr));
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn lookup_cold(c: &mut Criterion) {
|
||||
let mut table = ClaimTable::<MockTimeSource>::new(60, 60);
|
||||
let addr = Address::from_str("1.2.3.4").unwrap();
|
||||
table.set_claims(SocketAddr::from_str("1.2.3.4:3210").unwrap(), smallvec![Range::from_str("1.2.3.4/32").unwrap()]);
|
||||
let mut g = c.benchmark_group("table");
|
||||
g.throughput(Throughput::Bytes(1400));
|
||||
g.bench_function("lookup_cold", |b| {
|
||||
b.iter(|| {
|
||||
table.clear_cache();
|
||||
table.lookup(addr)
|
||||
});
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn crypto_bench(c: &mut Criterion, algo: &'static aead::Algorithm) {
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.set_length(1400);
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let mut g = c.benchmark_group("crypto");
|
||||
g.throughput(Throughput::Bytes(2*1400));
|
||||
g.bench_function(format!("{:?}", algo), |b| {
|
||||
b.iter(|| {
|
||||
sender.encrypt(&mut buffer);
|
||||
receiver.decrypt(&mut buffer).unwrap();
|
||||
});
|
||||
});
|
||||
g.finish()
|
||||
}
|
||||
|
||||
fn crypto_chacha20(c: &mut Criterion) {
|
||||
crypto_bench(c, &aead::CHACHA20_POLY1305)
|
||||
}
|
||||
|
||||
fn crypto_aes128(c: &mut Criterion) {
|
||||
crypto_bench(c, &aead::AES_128_GCM)
|
||||
}
|
||||
|
||||
fn crypto_aes256(c: &mut Criterion) {
|
||||
crypto_bench(c, &aead::AES_256_GCM)
|
||||
}
|
||||
|
||||
fn full_communication_tun_router(c: &mut Criterion) {
|
||||
log::set_max_level(log::LevelFilter::Error);
|
||||
let config1 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["1.1.1.1/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let config2 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["2.2.2.2/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let mut sim = TunSimulator::new();
|
||||
let node1 = sim.add_node(false, &config1);
|
||||
let node2 = sim.add_node(false, &config2);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let mut payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
||||
payload.append(&mut vec![0; 1400]);
|
||||
let mut g = c.benchmark_group("full_communication");
|
||||
g.throughput(Throughput::Bytes(2*1400));
|
||||
g.bench_function("tun_router", |b| {
|
||||
b.iter(|| {
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
assert_eq!(Some(&payload), sim.pop_payload(node2).as_ref());
|
||||
});
|
||||
});
|
||||
g.finish()
|
||||
}
|
||||
|
||||
fn full_communication_tap_switch(c: &mut Criterion) {
|
||||
log::set_max_level(log::LevelFilter::Error);
|
||||
let config = Config { device_type: Type::Tap, ..Config::default() };
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let mut payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5];
|
||||
payload.append(&mut vec![0; 1400]);
|
||||
let mut g = c.benchmark_group("full_communication");
|
||||
g.throughput(Throughput::Bytes(2*1400));
|
||||
g.bench_function("tap_switch", |b| {
|
||||
b.iter(|| {
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
assert_eq!(Some(&payload), sim.pop_payload(node2).as_ref());
|
||||
});
|
||||
});
|
||||
g.finish()
|
||||
}
|
||||
|
||||
criterion_group!(benches,
|
||||
udp_send,
|
||||
decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan,
|
||||
lookup_cold, lookup_warm,
|
||||
crypto_chacha20, crypto_aes128, crypto_aes256,
|
||||
full_communication_tun_router, full_communication_tap_switch
|
||||
);
|
||||
criterion_main!(benches);
|
|
@ -1,155 +0,0 @@
|
|||
#![allow(dead_code, unused_macros, unused_imports)]
|
||||
#[macro_use] extern crate serde;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
use iai::{black_box, main};
|
||||
|
||||
use smallvec::smallvec;
|
||||
use ring::aead;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket};
|
||||
|
||||
include!(".code.rs");
|
||||
|
||||
pub use error::Error;
|
||||
use util::{MockTimeSource, MsgBuffer};
|
||||
use config::Config;
|
||||
use types::{Address, Range};
|
||||
use device::Type;
|
||||
use table::ClaimTable;
|
||||
use payload::{Packet, Frame, Protocol};
|
||||
use crypto::core::{create_dummy_pair, EXTRA_LEN};
|
||||
use tests::common::{TunSimulator, TapSimulator};
|
||||
|
||||
fn udp_send() {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let data = [0; 1400];
|
||||
let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1);
|
||||
sock.send_to(&data, black_box(addr)).unwrap();
|
||||
}
|
||||
|
||||
fn decode_ipv4() {
|
||||
let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2];
|
||||
Packet::parse(&black_box(data)).unwrap();
|
||||
}
|
||||
|
||||
fn decode_ipv6() {
|
||||
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,
|
||||
4, 3, 2, 1
|
||||
];
|
||||
Packet::parse(&black_box(data)).unwrap();
|
||||
}
|
||||
|
||||
fn decode_ethernet() {
|
||||
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
Frame::parse(&black_box(data)).unwrap();
|
||||
}
|
||||
|
||||
fn decode_ethernet_with_vlan() {
|
||||
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0, 4, 210, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
Frame::parse(&black_box(data)).unwrap();
|
||||
}
|
||||
|
||||
fn lookup_warm() {
|
||||
let mut table = ClaimTable::<MockTimeSource>::new(60, 60);
|
||||
let addr = Address::from_str("1.2.3.4").unwrap();
|
||||
table.cache(addr, SocketAddr::from_str("1.2.3.4:3210").unwrap());
|
||||
for _ in 0..1000 {
|
||||
table.lookup(black_box(addr));
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_cold() {
|
||||
let mut table = ClaimTable::<MockTimeSource>::new(60, 60);
|
||||
let addr = Address::from_str("1.2.3.4").unwrap();
|
||||
table.set_claims(SocketAddr::from_str("1.2.3.4:3210").unwrap(), smallvec![Range::from_str("1.2.3.4/32").unwrap()]);
|
||||
for _ in 0..1000 {
|
||||
table.clear_cache();
|
||||
table.lookup(black_box(addr));
|
||||
}
|
||||
}
|
||||
|
||||
fn crypto_bench(algo: &'static aead::Algorithm) {
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.set_length(1400);
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
for _ in 0..1000 {
|
||||
sender.encrypt(black_box(&mut buffer));
|
||||
receiver.decrypt(&mut buffer).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn crypto_chacha20() {
|
||||
crypto_bench(&aead::CHACHA20_POLY1305)
|
||||
}
|
||||
|
||||
fn crypto_aes128() {
|
||||
crypto_bench(&aead::AES_128_GCM)
|
||||
}
|
||||
|
||||
fn crypto_aes256() {
|
||||
crypto_bench(&aead::AES_256_GCM)
|
||||
}
|
||||
|
||||
fn full_communication_tun_router() {
|
||||
log::set_max_level(log::LevelFilter::Error);
|
||||
let config1 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["1.1.1.1/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let config2 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["2.2.2.2/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let mut sim = TunSimulator::new();
|
||||
let node1 = sim.add_node(false, &config1);
|
||||
let node2 = sim.add_node(false, &config2);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let mut payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
||||
payload.append(&mut vec![0; 1400]);
|
||||
for _ in 0..1000 {
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
assert_eq!(Some(&payload), black_box(sim.pop_payload(node2).as_ref()));
|
||||
}
|
||||
}
|
||||
|
||||
fn full_communication_tap_switch() {
|
||||
log::set_max_level(log::LevelFilter::Error);
|
||||
let config = Config { device_type: Type::Tap, ..Config::default() };
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let mut payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5];
|
||||
payload.append(&mut vec![0; 1400]);
|
||||
for _ in 0..1000 {
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
assert_eq!(Some(&payload), black_box(sim.pop_payload(node2).as_ref()));
|
||||
}
|
||||
}
|
||||
|
||||
iai::main!(
|
||||
udp_send,
|
||||
decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan,
|
||||
lookup_cold, lookup_warm,
|
||||
crypto_chacha20, crypto_aes128, crypto_aes256,
|
||||
full_communication_tun_router, full_communication_tap_switch
|
||||
);
|
|
@ -0,0 +1,7 @@
|
|||
extern crate gcc;
|
||||
extern crate pkg_config;
|
||||
|
||||
fn main() {
|
||||
pkg_config::find_library("libsodium").unwrap();
|
||||
gcc::Config::new().file("src/c/tuntap.c").include("src").compile("libtuntap.a");
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
FROM ubuntu
|
||||
|
||||
RUN apt-get update && apt-get install -y asciinema locales bash iputils-ping
|
||||
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 /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
|
|
@ -1,3 +0,0 @@
|
|||
[record]
|
||||
command = bash -l
|
||||
idle_time_limit = 2.5
|
|
@ -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
|
|
@ -1,81 +0,0 @@
|
|||
AWSTemplateFormatVersion: 2010-09-09
|
||||
Description: |
|
||||
VpnCloud Websocket Proxy
|
||||
This will configure a websocket proxy to be used with VpnCloud.
|
||||
Versions: Ubuntu Server 20.04 LTS + VpnCloud 2.1.0
|
||||
Parameters:
|
||||
LatestAmiId:
|
||||
Description: "Image to use (just leave this as it is)"
|
||||
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
|
||||
Default: '/aws/service/canonical/ubuntu/server/20.04/stable/current/arm64/hvm/ebs-gp2/ami-id'
|
||||
AllowedValues:
|
||||
- '/aws/service/canonical/ubuntu/server/20.04/stable/current/arm64/hvm/ebs-gp2/ami-id'
|
||||
Resources:
|
||||
ProxySecurityGroup:
|
||||
Type: 'AWS::EC2::SecurityGroup'
|
||||
Properties:
|
||||
GroupDescription: Enable HTTP access via port 80 and any UDP port
|
||||
SecurityGroupIngress:
|
||||
- IpProtocol: tcp
|
||||
FromPort: '80'
|
||||
ToPort: '80'
|
||||
CidrIp: 0.0.0.0/0
|
||||
- IpProtocol: udp
|
||||
FromPort: '1024'
|
||||
ToPort: '65535'
|
||||
CidrIp: 0.0.0.0/0
|
||||
LaunchTemplate:
|
||||
Type: AWS::EC2::LaunchTemplate
|
||||
DependsOn:
|
||||
- ProxySecurityGroup
|
||||
Properties:
|
||||
LaunchTemplateData:
|
||||
ImageId: !Ref LatestAmiId
|
||||
SecurityGroups:
|
||||
- !Ref ProxySecurityGroup
|
||||
InstanceMarketOptions:
|
||||
MarketType: spot
|
||||
InstanceType: t4g.nano
|
||||
TagSpecifications:
|
||||
- ResourceType: instance
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: VpnCloud WS Proxy
|
||||
CreditSpecification:
|
||||
CpuCredits: standard
|
||||
BlockDeviceMappings:
|
||||
- DeviceName: /dev/sda1
|
||||
Ebs:
|
||||
VolumeType: standard
|
||||
VolumeSize: '8'
|
||||
DeleteOnTermination: 'true'
|
||||
Encrypted: 'false'
|
||||
UserData:
|
||||
Fn::Base64: !Sub |
|
||||
#cloud-config
|
||||
packages:
|
||||
- iperf3
|
||||
- socat
|
||||
runcmd:
|
||||
- wget https://github.com/dswd/vpncloud/releases/download/v2.1.0/vpncloud_2.1.0_arm64.deb -O /tmp/vpncloud.deb
|
||||
- dpkg -i /tmp/vpncloud.deb
|
||||
- nohup vpncloud ws-proxy -l 80 &
|
||||
ProxyInstance:
|
||||
Type: 'AWS::EC2::Instance'
|
||||
DependsOn:
|
||||
- LaunchTemplate
|
||||
Properties:
|
||||
LaunchTemplate:
|
||||
LaunchTemplateId:
|
||||
Ref: LaunchTemplate
|
||||
Version: 1
|
||||
Outputs:
|
||||
ProxyURL:
|
||||
Description: URL to use in VpnCloud config
|
||||
Value: !Join
|
||||
- ''
|
||||
- - 'ws://'
|
||||
- !GetAtt
|
||||
- ProxyInstance
|
||||
- PublicDnsName
|
||||
- ':80'
|
|
@ -1,384 +0,0 @@
|
|||
import boto3
|
||||
import atexit
|
||||
import paramiko
|
||||
import io
|
||||
import time
|
||||
import threading
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import sys
|
||||
import os
|
||||
from datetime import date
|
||||
|
||||
MAX_WAIT = 300
|
||||
CREATE = "***CREATE***"
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
def run_cmd(connection, cmd):
|
||||
_stdin, stdout, stderr = connection.exec_command(cmd)
|
||||
out = stdout.read().decode('utf-8')
|
||||
err = stderr.read().decode('utf-8')
|
||||
code = stdout.channel.recv_exit_status()
|
||||
if code:
|
||||
raise Exception("Command failed", code, out, err)
|
||||
else:
|
||||
return out, err
|
||||
|
||||
def upload(connection, local, remote):
|
||||
ftp_client=connection.open_sftp()
|
||||
ftp_client.put(local, remote)
|
||||
ftp_client.close()
|
||||
|
||||
class SpotInstanceRequest:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, instance, connection):
|
||||
self.instance = instance
|
||||
self.connection = connection
|
||||
self.private_ip = instance.private_ip_address
|
||||
self.public_ip = instance.public_ip_address
|
||||
|
||||
def run_cmd(self, cmd):
|
||||
return run_cmd(self.connection, cmd)
|
||||
|
||||
def start_vpncloud(self, ip=None, crypto=None, password="test", device_type="tun", listen="3210", mode="normal", peers=[], claims=[]):
|
||||
args = [
|
||||
"--daemon",
|
||||
"--no-port-forwarding",
|
||||
"-t {}".format(device_type),
|
||||
"-m {}".format(mode),
|
||||
"-l {}".format(listen),
|
||||
"--password '{}'".format(password)
|
||||
]
|
||||
if ip:
|
||||
args.append("--ip {}".format(ip))
|
||||
if crypto:
|
||||
args.append("--algo {}".format(crypto))
|
||||
for p in peers:
|
||||
args.append("-c {}".format(p))
|
||||
for c in claims:
|
||||
args.append("--claim {}".format(c))
|
||||
args = " ".join(args)
|
||||
self.run_cmd("sudo vpncloud {}".format(args))
|
||||
|
||||
def stop_vpncloud(self, wait=True):
|
||||
self.run_cmd("sudo killall vpncloud")
|
||||
if wait:
|
||||
time.sleep(3.0)
|
||||
|
||||
def ping(self, dst, size=100, count=10, interval=0.001):
|
||||
(out, _) = self.run_cmd('sudo ping {dst} -c {count} -i {interval} -s {size} -U -q'.format(dst=dst, size=size, count=count, interval=interval))
|
||||
match = re.search(r'([\d]*\.[\d]*)/([\d]*\.[\d]*)/([\d]*\.[\d]*)/([\d]*\.[\d]*)', out)
|
||||
ping_min = float(match.group(1))
|
||||
ping_avg = float(match.group(2))
|
||||
ping_max = float(match.group(3))
|
||||
match = re.search(r'(\d*)% packet loss', out)
|
||||
pkt_loss = float(match.group(1))
|
||||
return {
|
||||
"rtt_min": ping_min,
|
||||
"rtt_max": ping_max,
|
||||
"rtt_avg": ping_avg,
|
||||
"pkt_loss": pkt_loss
|
||||
}
|
||||
|
||||
def start_iperf_server(self):
|
||||
self.run_cmd('iperf3 -s -D')
|
||||
time.sleep(0.1)
|
||||
|
||||
def stop_iperf_server(self):
|
||||
self.run_cmd('killall iperf3')
|
||||
|
||||
def run_iperf(self, dst, duration):
|
||||
(out, _) = self.run_cmd('iperf3 -c {dst} -t {duration} --json'.format(dst=dst, duration=duration))
|
||||
data = json.loads(out)
|
||||
return {
|
||||
"throughput": data['end']['streams'][0]['receiver']['bits_per_second'],
|
||||
"cpu_sender": data['end']['cpu_utilization_percent']['host_total'],
|
||||
"cpu_receiver": data['end']['cpu_utilization_percent']['remote_total']
|
||||
}
|
||||
|
||||
|
||||
def find_ami(region, owner, name_pattern, arch='x86_64'):
|
||||
ec2client = boto3.client('ec2', region_name=region)
|
||||
response = ec2client.describe_images(Owners=[owner], Filters=[
|
||||
{'Name': 'name', 'Values': [name_pattern]},
|
||||
{'Name': 'architecture', 'Values': ['x86_64']}
|
||||
])
|
||||
try:
|
||||
image = max(response['Images'], key=lambda i: i['CreationDate'])
|
||||
return image['ImageId']
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class EC2Environment:
|
||||
def __init__(self, vpncloud_version, region, node_count, instance_type, vpncloud_file=None, use_spot=True, max_price=0.1, ami=('amazon', 'al2023-ami-*-hvm-*'), username="ec2-user", subnet=CREATE, keyname=CREATE, privatekey=CREATE, tag="vpncloud", cluster_nodes=False):
|
||||
self.region = region
|
||||
self.node_count = node_count
|
||||
self.instance_type = instance_type
|
||||
self.use_spot = use_spot
|
||||
self.max_price = str(max_price)
|
||||
if isinstance(ami, tuple):
|
||||
owner, name = ami
|
||||
self.ami = find_ami(region, owner, name)
|
||||
assert self.ami
|
||||
else:
|
||||
self.ami = ami
|
||||
self.username = username
|
||||
self.vpncloud_version = vpncloud_version
|
||||
self.vpncloud_file = vpncloud_file
|
||||
self.cluster_nodes = cluster_nodes
|
||||
self.resources = []
|
||||
self.instances = []
|
||||
self.connections = []
|
||||
self.nodes = []
|
||||
self.subnet = subnet
|
||||
self.tag = tag
|
||||
self.keyname = keyname
|
||||
self.privatekey = privatekey
|
||||
self.rsa_key = None
|
||||
try:
|
||||
eprint("Setting up resources...")
|
||||
self.setup()
|
||||
self.wait_until_ready()
|
||||
for i in range(0, self.node_count):
|
||||
self.nodes.append(Node(self.instances[i], self.connections[i]))
|
||||
eprint("Setup done")
|
||||
atexit.register(lambda : self.terminate())
|
||||
eprint()
|
||||
except:
|
||||
eprint("Error, shutting down")
|
||||
self.terminate()
|
||||
raise
|
||||
|
||||
def track_resource(self, res):
|
||||
self.resources.append(res)
|
||||
eprint("\t{} {}".format(res.__class__.__name__, res.id if hasattr(res, "id") else ""))
|
||||
if hasattr(res, "create_tags") and not hasattr(res, "name"):
|
||||
res.create_tags(Tags=[{"Key": "Name", "Value": self.tag}])
|
||||
|
||||
def setup_vpc(self):
|
||||
ec2 = boto3.resource('ec2', region_name=self.region)
|
||||
ec2client = boto3.client('ec2', region_name=self.region)
|
||||
|
||||
vpc = ec2.create_vpc(CidrBlock='172.16.0.0/16')
|
||||
self.track_resource(vpc)
|
||||
vpc.wait_until_available()
|
||||
ec2client.modify_vpc_attribute(VpcId=vpc.id, EnableDnsSupport={'Value': True})
|
||||
ec2client.modify_vpc_attribute(VpcId=vpc.id, EnableDnsHostnames={'Value': True})
|
||||
|
||||
igw = ec2.create_internet_gateway()
|
||||
self.track_resource(igw)
|
||||
igw.attach_to_vpc(VpcId=vpc.id)
|
||||
|
||||
rtb = vpc.create_route_table()
|
||||
self.track_resource(rtb)
|
||||
rtb.create_route(DestinationCidrBlock='0.0.0.0/0', GatewayId=igw.id)
|
||||
|
||||
subnet = ec2.create_subnet(CidrBlock='172.16.1.0/24', VpcId=vpc.id)
|
||||
self.track_resource(subnet)
|
||||
rtb.associate_with_subnet(SubnetId=subnet.id)
|
||||
|
||||
self.subnet = subnet.id
|
||||
|
||||
|
||||
def setup(self):
|
||||
ec2 = boto3.resource('ec2', region_name=self.region)
|
||||
ec2client = boto3.client('ec2', region_name=self.region)
|
||||
|
||||
if self.subnet == CREATE:
|
||||
self.setup_vpc()
|
||||
else:
|
||||
eprint("\tUsing subnet {}".format(self.subnet))
|
||||
|
||||
vpc = ec2.Subnet(self.subnet).vpc
|
||||
|
||||
sg = ec2.create_security_group(GroupName='SSH-ONLY', Description='only allow SSH traffic', VpcId=vpc.id)
|
||||
self.track_resource(sg)
|
||||
sg.authorize_ingress(CidrIp='0.0.0.0/0', IpProtocol='tcp', FromPort=22, ToPort=22)
|
||||
sg.authorize_ingress(CidrIp='172.16.1.0/24', IpProtocol='icmp', FromPort=-1, ToPort=-1)
|
||||
sg.authorize_ingress(CidrIp='172.16.1.0/24', IpProtocol='tcp', FromPort=0, ToPort=65535)
|
||||
sg.authorize_ingress(CidrIp='172.16.1.0/24', IpProtocol='udp', FromPort=0, ToPort=65535)
|
||||
|
||||
if self.keyname == CREATE:
|
||||
key_pair = ec2.create_key_pair(KeyName="{}-keypair".format(self.tag))
|
||||
self.track_resource(key_pair)
|
||||
self.keyname = key_pair.name
|
||||
self.privatekey = key_pair.key_material
|
||||
self.rsa_key = paramiko.RSAKey.from_private_key(io.StringIO(self.privatekey))
|
||||
|
||||
placement = {}
|
||||
if self.cluster_nodes:
|
||||
placement_group = ec2.create_placement_group(GroupName="{}-placement".format(self.tag), Strategy="cluster")
|
||||
self.track_resource(placement_group)
|
||||
placement = { 'GroupName': placement_group.name }
|
||||
|
||||
userdata = """#cloud-config
|
||||
packages:
|
||||
- iperf3
|
||||
- socat
|
||||
"""
|
||||
if not self.vpncloud_file:
|
||||
userdata += """
|
||||
runcmd:
|
||||
- wget https://github.com/dswd/vpncloud/releases/download/v{version}/vpncloud_{version}.x86_64.rpm -O /tmp/vpncloud.rpm
|
||||
- yum install -y /tmp/vpncloud.rpm
|
||||
""".format(version=self.vpncloud_version)
|
||||
|
||||
if self.use_spot:
|
||||
response = ec2client.request_spot_instances(
|
||||
SpotPrice = self.max_price,
|
||||
Type = "one-time",
|
||||
InstanceCount = self.node_count,
|
||||
LaunchSpecification = {
|
||||
"ImageId": self.ami,
|
||||
"InstanceType": self.instance_type,
|
||||
"KeyName": key_pair.name,
|
||||
"UserData": base64.b64encode(userdata.encode("ascii")).decode('ascii'),
|
||||
"BlockDeviceMappings": [
|
||||
{
|
||||
"DeviceName": "/dev/xvda",
|
||||
"Ebs": {
|
||||
"DeleteOnTermination": True,
|
||||
"VolumeType": "gp2",
|
||||
}
|
||||
}
|
||||
],
|
||||
"NetworkInterfaces": [
|
||||
{
|
||||
'SubnetId': self.subnet,
|
||||
'DeviceIndex': 0,
|
||||
'AssociatePublicIpAddress': True,
|
||||
'Groups': [sg.group_id]
|
||||
}
|
||||
],
|
||||
"Placement": placement
|
||||
}
|
||||
)
|
||||
requests = []
|
||||
for req in response['SpotInstanceRequests']:
|
||||
request = SpotInstanceRequest(req['SpotInstanceRequestId'])
|
||||
self.track_resource(request)
|
||||
requests.append(request)
|
||||
eprint("Waiting for spot instance requests")
|
||||
waited = 0
|
||||
self.instances = [None] * len(requests)
|
||||
while waited < MAX_WAIT:
|
||||
time.sleep(1.0)
|
||||
for i, req in enumerate(requests):
|
||||
response = ec2client.describe_spot_instance_requests(SpotInstanceRequestIds=[req.id])
|
||||
data = response['SpotInstanceRequests'][0]
|
||||
if 'InstanceId' in data:
|
||||
self.instances[i] = ec2.Instance(data['InstanceId'])
|
||||
self.track_resource(self.instances[i])
|
||||
if min(map(bool, self.instances)):
|
||||
break
|
||||
if waited >= MAX_WAIT:
|
||||
raise Exception("Waited too long")
|
||||
else:
|
||||
self.instances = ec2.create_instances(
|
||||
ImageId=self.ami,
|
||||
InstanceType=self.instance_type,
|
||||
MaxCount=self.node_count,
|
||||
MinCount=self.node_count,
|
||||
NetworkInterfaces=[
|
||||
{
|
||||
'SubnetId': self.subnet,
|
||||
'DeviceIndex': 0,
|
||||
'AssociatePublicIpAddress': True,
|
||||
'Groups': [sg.group_id]
|
||||
}
|
||||
],
|
||||
Placement=placement,
|
||||
UserData=userdata,
|
||||
KeyName=key_pair.name
|
||||
)
|
||||
for instance in self.instances:
|
||||
self.track_resource(instance)
|
||||
|
||||
def wait_until_ready(self):
|
||||
waited = 0
|
||||
eprint("Waiting for instances to start...")
|
||||
for instance in self.instances:
|
||||
instance.wait_until_running()
|
||||
instance.reload()
|
||||
eprint("Waiting for SSH to be ready...")
|
||||
self.connections = [None] * len(self.instances)
|
||||
while waited < MAX_WAIT:
|
||||
for i, instance in enumerate(self.instances):
|
||||
if self.connections[i]:
|
||||
continue
|
||||
try:
|
||||
self.connections[i] = self._connect(instance)
|
||||
except:
|
||||
pass
|
||||
if min(map(bool, self.connections)):
|
||||
break
|
||||
time.sleep(1.0)
|
||||
waited += 1
|
||||
eprint("Waiting for instances to finish setup...")
|
||||
ready = [False] * len(self.connections)
|
||||
while waited < MAX_WAIT:
|
||||
for i, con in enumerate(self.connections):
|
||||
if ready[i]:
|
||||
continue
|
||||
try:
|
||||
run_cmd(con, 'test -f /var/lib/cloud/instance/boot-finished')
|
||||
ready[i] = True
|
||||
except:
|
||||
pass
|
||||
if min(map(bool, ready)):
|
||||
break
|
||||
time.sleep(1.0)
|
||||
waited += 1
|
||||
if waited >= MAX_WAIT:
|
||||
raise Exception("Waited too long")
|
||||
if self.vpncloud_file:
|
||||
eprint("Uploading vpncloud binary")
|
||||
for con in self.connections:
|
||||
upload(con, self.vpncloud_file, 'vpncloud')
|
||||
run_cmd(con, 'chmod +x vpncloud')
|
||||
run_cmd(con, 'sudo mv vpncloud /usr/bin/vpncloud')
|
||||
|
||||
|
||||
def terminate(self):
|
||||
if not self.resources:
|
||||
return
|
||||
eprint("Closing connections...")
|
||||
for con in self.connections:
|
||||
if con:
|
||||
con.close()
|
||||
self.connections = []
|
||||
eprint("Terminating instances...")
|
||||
for instance in self.instances:
|
||||
instance.terminate()
|
||||
for instance in self.instances:
|
||||
eprint("\t{}".format(instance.id))
|
||||
instance.wait_until_terminated()
|
||||
self.instances = []
|
||||
eprint("Deleting resources...")
|
||||
ec2client = boto3.client('ec2', region_name=self.region)
|
||||
for res in reversed(self.resources):
|
||||
eprint("\t{} {}".format(res.__class__.__name__, res.id if hasattr(res, "id") else ""))
|
||||
if isinstance(res, SpotInstanceRequest):
|
||||
ec2client.cancel_spot_instance_requests(SpotInstanceRequestIds=[res.id])
|
||||
if hasattr(res, "attachments"):
|
||||
for a in res.attachments:
|
||||
res.detach_from_vpc(VpcId=a['VpcId'])
|
||||
if hasattr(res, "delete"):
|
||||
res.delete()
|
||||
self.resources = []
|
||||
|
||||
def _connect(self, instance):
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect(hostname=instance.public_dns_name, username=self.username, pkey=self.rsa_key, timeout=1.0, banner_timeout=1.0)
|
||||
return client
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from common import EC2Environment, CREATE
|
||||
import time
|
||||
|
||||
setup = EC2Environment(
|
||||
region = "eu-central-1",
|
||||
node_count = 2,
|
||||
instance_type = 't3a.nano',
|
||||
vpncloud_version = "2.1.0"
|
||||
)
|
||||
|
||||
sender = setup.nodes[0]
|
||||
receiver = setup.nodes[1]
|
||||
|
||||
sender.start_vpncloud(ip="10.0.0.1/24")
|
||||
receiver.start_vpncloud(ip="10.0.0.2/24", peers=["{}:3210".format(sender.private_ip)])
|
||||
time.sleep(1.0)
|
||||
|
||||
sender.ping("10.0.0.2")
|
||||
|
||||
sender.stop_vpncloud()
|
||||
receiver.stop_vpncloud()
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-0a02ee601d742e89f",
|
||||
"version": "1.0.0",
|
||||
"duration": 495.34057664871216
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9680305000.0,
|
||||
"cpu_sender": 20.862659,
|
||||
"cpu_receiver": 65.856166
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.035,
|
||||
"rtt_max": 0.121,
|
||||
"rtt_avg": 0.04,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.036,
|
||||
"rtt_max": 0.151,
|
||||
"rtt_avg": 0.042,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.039,
|
||||
"rtt_max": 0.145,
|
||||
"rtt_avg": 0.043,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 4836527000.0,
|
||||
"cpu_sender": 9.380104,
|
||||
"cpu_receiver": 65.333537
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.06,
|
||||
"rtt_max": 0.165,
|
||||
"rtt_avg": 0.074,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.063,
|
||||
"rtt_max": 0.389,
|
||||
"rtt_avg": 0.076,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.066,
|
||||
"rtt_max": 0.185,
|
||||
"rtt_avg": 0.077,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3312712000.0,
|
||||
"cpu_sender": 7.639525,
|
||||
"cpu_receiver": 54.563243
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.062,
|
||||
"rtt_max": 1.352,
|
||||
"rtt_avg": 0.075,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.064,
|
||||
"rtt_max": 0.163,
|
||||
"rtt_avg": 0.077,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.067,
|
||||
"rtt_max": 0.334,
|
||||
"rtt_avg": 0.079,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2418750000.0,
|
||||
"cpu_sender": 4.331642,
|
||||
"cpu_receiver": 49.452792
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.065,
|
||||
"rtt_max": 0.182,
|
||||
"rtt_avg": 0.08,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.072,
|
||||
"rtt_max": 0.201,
|
||||
"rtt_avg": 0.086,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.075,
|
||||
"rtt_max": 0.38,
|
||||
"rtt_avg": 0.09,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9680.305,
|
||||
"plain": 4836.527,
|
||||
"aes256": 3312.712,
|
||||
"chacha20": 2418.75
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 16.999999999999996,
|
||||
"500": 16.999999999999996,
|
||||
"1000": 17.0
|
||||
},
|
||||
"aes256": {
|
||||
"100": 17.499999999999996,
|
||||
"500": 17.499999999999996,
|
||||
"1000": 18.000000000000004
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 20.0,
|
||||
"500": 21.999999999999996,
|
||||
"1000": 23.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-0a02ee601d742e89f",
|
||||
"version": "1.1.0",
|
||||
"duration": 495.3976471424103
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9672876000.0,
|
||||
"cpu_sender": 21.627575,
|
||||
"cpu_receiver": 68.637173
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.049,
|
||||
"rtt_max": 0.869,
|
||||
"rtt_avg": 0.057,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.051,
|
||||
"rtt_max": 13.136,
|
||||
"rtt_avg": 0.059,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.054,
|
||||
"rtt_max": 0.179,
|
||||
"rtt_avg": 0.06,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 4983797000.0,
|
||||
"cpu_sender": 11.27702,
|
||||
"cpu_receiver": 65.580003
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.077,
|
||||
"rtt_max": 0.24,
|
||||
"rtt_avg": 0.093,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 13.188,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.083,
|
||||
"rtt_max": 0.223,
|
||||
"rtt_avg": 0.097,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3226869000.0,
|
||||
"cpu_sender": 5.683309,
|
||||
"cpu_receiver": 54.09244
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.077,
|
||||
"rtt_max": 1.533,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.082,
|
||||
"rtt_max": 13.21,
|
||||
"rtt_avg": 0.098,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.084,
|
||||
"rtt_max": 0.249,
|
||||
"rtt_avg": 0.099,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2740356000.0,
|
||||
"cpu_sender": 7.041418,
|
||||
"cpu_receiver": 56.705647
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.081,
|
||||
"rtt_max": 0.299,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.087,
|
||||
"rtt_max": 13.089,
|
||||
"rtt_avg": 0.104,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.092,
|
||||
"rtt_max": 0.36,
|
||||
"rtt_avg": 0.108,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9672.876,
|
||||
"plain": 4983.797,
|
||||
"aes256": 3226.869,
|
||||
"chacha20": 2740.356
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 18.0,
|
||||
"500": 18.500000000000004,
|
||||
"1000": 18.500000000000004
|
||||
},
|
||||
"aes256": {
|
||||
"100": 19.0,
|
||||
"500": 19.500000000000004,
|
||||
"1000": 19.500000000000004
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 19.5,
|
||||
"500": 22.5,
|
||||
"1000": 24.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-0a02ee601d742e89f",
|
||||
"version": "1.2.0",
|
||||
"duration": 495.3082287311554
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9679928000.0,
|
||||
"cpu_sender": 14.518989,
|
||||
"cpu_receiver": 75.510689
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.044,
|
||||
"rtt_max": 0.212,
|
||||
"rtt_avg": 0.052,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.049,
|
||||
"rtt_max": 0.368,
|
||||
"rtt_avg": 0.053,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.049,
|
||||
"rtt_max": 0.422,
|
||||
"rtt_avg": 0.055,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 5670752000.0,
|
||||
"cpu_sender": 15.711913,
|
||||
"cpu_receiver": 69.661585
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.076,
|
||||
"rtt_max": 0.298,
|
||||
"rtt_avg": 0.092,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.269,
|
||||
"rtt_avg": 0.094,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.469,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3531058000.0,
|
||||
"cpu_sender": 5.652232,
|
||||
"cpu_receiver": 58.238361
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 0.849,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 0.205,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.083,
|
||||
"rtt_max": 0.418,
|
||||
"rtt_avg": 0.099,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2384829000.0,
|
||||
"cpu_sender": 3.995289,
|
||||
"cpu_receiver": 47.656852
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 0.2,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.087,
|
||||
"rtt_max": 0.223,
|
||||
"rtt_avg": 0.102,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.092,
|
||||
"rtt_max": 0.416,
|
||||
"rtt_avg": 0.109,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9679.928,
|
||||
"plain": 5670.752,
|
||||
"aes256": 3531.058,
|
||||
"chacha20": 2384.829
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 20.0,
|
||||
"500": 20.5,
|
||||
"1000": 20.0
|
||||
},
|
||||
"aes256": {
|
||||
"100": 21.5,
|
||||
"500": 21.5,
|
||||
"1000": 22.000000000000004
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 22.000000000000004,
|
||||
"500": 24.499999999999996,
|
||||
"1000": 27.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-0a02ee601d742e89f",
|
||||
"version": "1.3.0",
|
||||
"duration": 495.4212408065796
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9673825000.0,
|
||||
"cpu_sender": 14.002781,
|
||||
"cpu_receiver": 74.93156
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.04,
|
||||
"rtt_max": 0.235,
|
||||
"rtt_avg": 0.047,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.042,
|
||||
"rtt_max": 0.161,
|
||||
"rtt_avg": 0.048,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.043,
|
||||
"rtt_max": 0.36,
|
||||
"rtt_avg": 0.049,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 5335762000.0,
|
||||
"cpu_sender": 14.483975,
|
||||
"cpu_receiver": 66.013613
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.069,
|
||||
"rtt_max": 0.183,
|
||||
"rtt_avg": 0.084,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.074,
|
||||
"rtt_max": 0.185,
|
||||
"rtt_avg": 0.088,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.077,
|
||||
"rtt_max": 0.203,
|
||||
"rtt_avg": 0.089,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3488220000.0,
|
||||
"cpu_sender": 5.49698,
|
||||
"cpu_receiver": 57.458403
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.073,
|
||||
"rtt_max": 1.55,
|
||||
"rtt_avg": 0.087,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 3.637,
|
||||
"rtt_avg": 0.091,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.233,
|
||||
"rtt_avg": 0.092,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2784941000.0,
|
||||
"cpu_sender": 4.273576,
|
||||
"cpu_receiver": 59.274818
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.073,
|
||||
"rtt_max": 9.541,
|
||||
"rtt_avg": 0.092,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.084,
|
||||
"rtt_max": 0.196,
|
||||
"rtt_avg": 0.098,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.089,
|
||||
"rtt_max": 0.614,
|
||||
"rtt_avg": 0.104,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9673.825,
|
||||
"plain": 5335.762,
|
||||
"aes256": 3488.22,
|
||||
"chacha20": 2784.941
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 18.500000000000004,
|
||||
"500": 19.999999999999996,
|
||||
"1000": 19.999999999999996
|
||||
},
|
||||
"aes256": {
|
||||
"100": 19.999999999999996,
|
||||
"500": 21.5,
|
||||
"1000": 21.5
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 22.5,
|
||||
"500": 25.0,
|
||||
"1000": 27.499999999999996
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-0a02ee601d742e89f",
|
||||
"version": "1.4.0",
|
||||
"duration": 495.3894383907318
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9679070000.0,
|
||||
"cpu_sender": 12.871267,
|
||||
"cpu_receiver": 71.50818
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.039,
|
||||
"rtt_max": 0.219,
|
||||
"rtt_avg": 0.047,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.041,
|
||||
"rtt_max": 0.184,
|
||||
"rtt_avg": 0.048,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.042,
|
||||
"rtt_max": 0.303,
|
||||
"rtt_avg": 0.049,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 4991320000.0,
|
||||
"cpu_sender": 8.543496,
|
||||
"cpu_receiver": 65.730322
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.071,
|
||||
"rtt_max": 3.557,
|
||||
"rtt_avg": 0.087,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.075,
|
||||
"rtt_max": 0.726,
|
||||
"rtt_avg": 0.088,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.076,
|
||||
"rtt_max": 3.38,
|
||||
"rtt_avg": 0.091,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3824049000.0,
|
||||
"cpu_sender": 8.673973,
|
||||
"cpu_receiver": 62.240793
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.072,
|
||||
"rtt_max": 1.365,
|
||||
"rtt_avg": 0.09,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 0.236,
|
||||
"rtt_avg": 0.092,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.439,
|
||||
"rtt_avg": 0.094,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2838553000.0,
|
||||
"cpu_sender": 6.9273,
|
||||
"cpu_receiver": 60.482437
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.075,
|
||||
"rtt_max": 0.783,
|
||||
"rtt_avg": 0.093,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.086,
|
||||
"rtt_max": 0.208,
|
||||
"rtt_avg": 0.098,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.088,
|
||||
"rtt_max": 0.214,
|
||||
"rtt_avg": 0.103,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9679.07,
|
||||
"plain": 4991.32,
|
||||
"aes256": 3824.049,
|
||||
"chacha20": 2838.553
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 19.999999999999996,
|
||||
"500": 19.999999999999996,
|
||||
"1000": 20.999999999999996
|
||||
},
|
||||
"aes256": {
|
||||
"100": 21.5,
|
||||
"500": 22.0,
|
||||
"1000": 22.5
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 23.0,
|
||||
"500": 25.0,
|
||||
"1000": 26.999999999999996
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-00a205cb8e06c3c4e",
|
||||
"version": "2.0.0",
|
||||
"duration": 621.3780446052551
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9681224000.0,
|
||||
"cpu_sender": 13.679709,
|
||||
"cpu_receiver": 71.69651
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.045,
|
||||
"rtt_max": 0.18,
|
||||
"rtt_avg": 0.051,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.047,
|
||||
"rtt_max": 0.184,
|
||||
"rtt_avg": 0.054,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.049,
|
||||
"rtt_max": 0.175,
|
||||
"rtt_avg": 0.056,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 5472962000.0,
|
||||
"cpu_sender": 15.087884,
|
||||
"cpu_receiver": 67.570992
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 0.257,
|
||||
"rtt_avg": 0.093,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 0.243,
|
||||
"rtt_avg": 0.097,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 0.591,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3947676000.0,
|
||||
"cpu_sender": 6.859741,
|
||||
"cpu_receiver": 62.826154
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.081,
|
||||
"rtt_max": 1.653,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.081,
|
||||
"rtt_max": 1.259,
|
||||
"rtt_avg": 0.098,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.082,
|
||||
"rtt_max": 0.257,
|
||||
"rtt_avg": 0.099,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes128": {
|
||||
"iperf": {
|
||||
"throughput": 4200596000.0,
|
||||
"cpu_sender": 10.291266,
|
||||
"cpu_receiver": 64.395908
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.081,
|
||||
"rtt_max": 0.294,
|
||||
"rtt_avg": 0.097,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.084,
|
||||
"rtt_max": 0.238,
|
||||
"rtt_avg": 0.099,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.086,
|
||||
"rtt_max": 0.291,
|
||||
"rtt_avg": 0.101,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2854407000.0,
|
||||
"cpu_sender": 5.648368,
|
||||
"cpu_receiver": 58.473016
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.082,
|
||||
"rtt_max": 0.515,
|
||||
"rtt_avg": 0.098,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.089,
|
||||
"rtt_max": 3.457,
|
||||
"rtt_avg": 0.105,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.092,
|
||||
"rtt_max": 0.366,
|
||||
"rtt_avg": 0.108,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9681.224,
|
||||
"plain": 5472.962,
|
||||
"aes256": 3947.676,
|
||||
"aes128": 4200.596,
|
||||
"chacha20": 2854.407
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 21.0,
|
||||
"500": 21.5,
|
||||
"1000": 20.0
|
||||
},
|
||||
"aes256": {
|
||||
"100": 22.500000000000004,
|
||||
"500": 22.000000000000004,
|
||||
"1000": 21.5
|
||||
},
|
||||
"aes128": {
|
||||
"100": 23.000000000000004,
|
||||
"500": 22.500000000000004,
|
||||
"1000": 22.500000000000004
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 23.500000000000004,
|
||||
"500": 25.5,
|
||||
"1000": 26.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-0a6dc7529cd559185",
|
||||
"version": "2.1.0",
|
||||
"duration": 622.053159236908
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9672965000.0,
|
||||
"cpu_sender": 11.936759,
|
||||
"cpu_receiver": 70.348812
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.046,
|
||||
"rtt_max": 0.246,
|
||||
"rtt_avg": 0.053,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.048,
|
||||
"rtt_max": 0.183,
|
||||
"rtt_avg": 0.055,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.05,
|
||||
"rtt_max": 0.272,
|
||||
"rtt_avg": 0.057,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 5728527000.0,
|
||||
"cpu_sender": 11.004746,
|
||||
"cpu_receiver": 67.527328
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 0.372,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 0.272,
|
||||
"rtt_avg": 0.094,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.082,
|
||||
"rtt_max": 0.217,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3706944000.0,
|
||||
"cpu_sender": 6.465523,
|
||||
"cpu_receiver": 60.216674
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.28,
|
||||
"rtt_avg": 0.097,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 13.372,
|
||||
"rtt_avg": 0.099,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.086,
|
||||
"rtt_max": 0.358,
|
||||
"rtt_avg": 0.102,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes128": {
|
||||
"iperf": {
|
||||
"throughput": 3876646000.0,
|
||||
"cpu_sender": 6.800352,
|
||||
"cpu_receiver": 61.738244
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.078,
|
||||
"rtt_max": 0.219,
|
||||
"rtt_avg": 0.096,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.083,
|
||||
"rtt_max": 0.232,
|
||||
"rtt_avg": 0.097,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.087,
|
||||
"rtt_max": 0.327,
|
||||
"rtt_avg": 0.099,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 2917879000.0,
|
||||
"cpu_sender": 5.066722,
|
||||
"cpu_receiver": 55.171241
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.081,
|
||||
"rtt_max": 0.283,
|
||||
"rtt_avg": 0.097,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.087,
|
||||
"rtt_max": 0.348,
|
||||
"rtt_avg": 0.103,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.088,
|
||||
"rtt_max": 0.309,
|
||||
"rtt_avg": 0.105,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9672.965,
|
||||
"plain": 5728.527,
|
||||
"aes256": 3706.944,
|
||||
"aes128": 3876.646,
|
||||
"chacha20": 2917.879
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 21.0,
|
||||
"500": 19.5,
|
||||
"1000": 19.5
|
||||
},
|
||||
"aes256": {
|
||||
"100": 22.000000000000004,
|
||||
"500": 22.000000000000004,
|
||||
"1000": 22.499999999999996
|
||||
},
|
||||
"aes128": {
|
||||
"100": 21.5,
|
||||
"500": 21.0,
|
||||
"1000": 21.0
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 22.000000000000004,
|
||||
"500": 23.999999999999996,
|
||||
"1000": 23.999999999999996
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
{
|
||||
"meta": {
|
||||
"region": "eu-central-1",
|
||||
"instance_type": "m5.large",
|
||||
"ami": "ami-099ccc441b2ef41ec",
|
||||
"version": "2.3.0",
|
||||
"duration": 622.5463161468506
|
||||
},
|
||||
"native": {
|
||||
"iperf": {
|
||||
"throughput": 9529265000.0,
|
||||
"cpu_sender": 11.32918,
|
||||
"cpu_receiver": 61.870429
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.046,
|
||||
"rtt_max": 0.225,
|
||||
"rtt_avg": 0.053,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.047,
|
||||
"rtt_max": 10.98,
|
||||
"rtt_avg": 0.054,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.048,
|
||||
"rtt_max": 0.175,
|
||||
"rtt_avg": 0.056,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"plain": {
|
||||
"iperf": {
|
||||
"throughput": 6388312000.0,
|
||||
"cpu_sender": 16.955082,
|
||||
"cpu_receiver": 72.705695
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.076,
|
||||
"rtt_max": 11.973,
|
||||
"rtt_avg": 0.09,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 10.95,
|
||||
"rtt_avg": 0.094,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.081,
|
||||
"rtt_max": 1.638,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes256": {
|
||||
"iperf": {
|
||||
"throughput": 3801851000.0,
|
||||
"cpu_sender": 5.826756,
|
||||
"cpu_receiver": 61.612033
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.075,
|
||||
"rtt_max": 0.9,
|
||||
"rtt_avg": 0.093,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.275,
|
||||
"rtt_avg": 0.091,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 1.015,
|
||||
"rtt_avg": 0.093,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"aes128": {
|
||||
"iperf": {
|
||||
"throughput": 3880325000.0,
|
||||
"cpu_sender": 6.219277,
|
||||
"cpu_receiver": 62.125445
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.077,
|
||||
"rtt_max": 11.656,
|
||||
"rtt_avg": 0.09,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.08,
|
||||
"rtt_max": 0.211,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.082,
|
||||
"rtt_max": 1.398,
|
||||
"rtt_avg": 0.095,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"chacha20": {
|
||||
"iperf": {
|
||||
"throughput": 3126447000.0,
|
||||
"cpu_sender": 5.113819,
|
||||
"cpu_receiver": 58.58095
|
||||
},
|
||||
"ping_100": {
|
||||
"rtt_min": 0.079,
|
||||
"rtt_max": 0.271,
|
||||
"rtt_avg": 0.091,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_500": {
|
||||
"rtt_min": 0.083,
|
||||
"rtt_max": 0.272,
|
||||
"rtt_avg": 0.098,
|
||||
"pkt_loss": 0.0
|
||||
},
|
||||
"ping_1000": {
|
||||
"rtt_min": 0.087,
|
||||
"rtt_max": 1.615,
|
||||
"rtt_avg": 0.101,
|
||||
"pkt_loss": 0.0
|
||||
}
|
||||
},
|
||||
"results": {
|
||||
"throughput_mbits": {
|
||||
"native": 9529.265,
|
||||
"plain": 6388.312,
|
||||
"aes256": 3801.851,
|
||||
"aes128": 3880.325,
|
||||
"chacha20": 3126.447
|
||||
},
|
||||
"latency_us": {
|
||||
"plain": {
|
||||
"100": 18.5,
|
||||
"500": 20.0,
|
||||
"1000": 19.5
|
||||
},
|
||||
"aes256": {
|
||||
"100": 20.0,
|
||||
"500": 18.5,
|
||||
"1000": 18.5
|
||||
},
|
||||
"aes128": {
|
||||
"100": 18.5,
|
||||
"500": 20.5,
|
||||
"1000": 19.5
|
||||
},
|
||||
"chacha20": {
|
||||
"100": 19.0,
|
||||
"500": 22.000000000000004,
|
||||
"1000": 22.500000000000004
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from common import EC2Environment, CREATE, eprint
|
||||
import time, json
|
||||
from datetime import date
|
||||
|
||||
|
||||
# Note: this script will run for ~8 minutes and incur costs of about $ 0.02
|
||||
|
||||
FILE = "../../target/release/vpncloud"
|
||||
VERSION = "2.4.0"
|
||||
REGION = "eu-central-1"
|
||||
|
||||
env = EC2Environment(
|
||||
region = REGION,
|
||||
node_count = 2,
|
||||
instance_type = "m5.large",
|
||||
use_spot = False,
|
||||
max_price = "0.08", # USD per hour per VM
|
||||
vpncloud_version = VERSION,
|
||||
vpncloud_file = FILE,
|
||||
cluster_nodes = True,
|
||||
subnet = CREATE,
|
||||
keyname = CREATE
|
||||
)
|
||||
|
||||
|
||||
CRYPTO = ["plain", "aes256", "aes128", "chacha20"]
|
||||
|
||||
|
||||
class PerfTest:
|
||||
def __init__(self, sender, receiver, meta):
|
||||
self.sender = sender
|
||||
self.receiver = receiver
|
||||
self.sender_ip_vpncloud = "10.0.0.1"
|
||||
self.receiver_ip_vpncloud = "10.0.0.2"
|
||||
self.meta = meta
|
||||
|
||||
@classmethod
|
||||
def from_ec2_env(cls, env):
|
||||
meta = {
|
||||
"region": env.region,
|
||||
"instance_type": env.instance_type,
|
||||
"ami": env.ami,
|
||||
"version": env.vpncloud_version
|
||||
}
|
||||
return cls(env.nodes[0], env.nodes[1], meta)
|
||||
|
||||
def run_ping(self, dst, size):
|
||||
eprint("\tRunning ping {} with size {} ...".format(dst, size))
|
||||
return self.sender.ping(dst=dst, size=size, count=30000, interval=0.001)
|
||||
|
||||
def run_iperf(self, dst):
|
||||
eprint("\tRunning iperf on {} ...".format(dst))
|
||||
self.receiver.start_iperf_server()
|
||||
time.sleep(0.1)
|
||||
result = self.sender.run_iperf(dst=dst, duration=30)
|
||||
self.receiver.stop_iperf_server()
|
||||
return result
|
||||
|
||||
def run_suite(self, dst):
|
||||
return {
|
||||
"iperf": self.run_iperf(dst),
|
||||
"ping_100": self.run_ping(dst, 100),
|
||||
"ping_500": self.run_ping(dst, 500),
|
||||
"ping_1000": self.run_ping(dst, 1000),
|
||||
}
|
||||
|
||||
def start_vpncloud(self, crypto=None):
|
||||
eprint("\tSetting up vpncloud on receiver")
|
||||
self.receiver.start_vpncloud(crypto=crypto, ip="{}/24".format(self.receiver_ip_vpncloud))
|
||||
eprint("\tSetting up vpncloud on sender")
|
||||
self.sender.start_vpncloud(crypto=crypto, peers=["{}:3210".format(self.receiver.private_ip)], ip="{}/24".format(self.sender_ip_vpncloud))
|
||||
time.sleep(1.0)
|
||||
|
||||
def stop_vpncloud(self):
|
||||
self.sender.stop_vpncloud(wait=False)
|
||||
self.receiver.stop_vpncloud(wait=True)
|
||||
|
||||
def run(self):
|
||||
eprint("Testing native network")
|
||||
results = {
|
||||
"meta": self.meta,
|
||||
"native": self.run_suite(self.receiver.private_ip)
|
||||
}
|
||||
for crypto in CRYPTO:
|
||||
eprint("Running with crypto {}".format(crypto))
|
||||
self.start_vpncloud(crypto=crypto)
|
||||
res = self.run_suite(self.receiver_ip_vpncloud)
|
||||
self.stop_vpncloud()
|
||||
results[str(crypto)] = res
|
||||
results['results'] = {
|
||||
"throughput_mbits": dict([
|
||||
(k, results[k]["iperf"]["throughput"] / 1000000.0) for k in ["native"] + CRYPTO
|
||||
]),
|
||||
"latency_us": dict([
|
||||
(k, dict([
|
||||
(str(s), (results[k]["ping_%s" % s]["rtt_avg"] - results["native"]["ping_%s" % s]["rtt_avg"])*1000.0/2.0) for s in [100, 500, 1000]
|
||||
])) for k in CRYPTO
|
||||
])
|
||||
}
|
||||
return results
|
||||
|
||||
perf = PerfTest.from_ec2_env(env)
|
||||
|
||||
start = time.time()
|
||||
results = perf.run()
|
||||
duration = time.time() - start
|
||||
|
||||
results["meta"]["duration"] = duration
|
||||
|
||||
name = "measurements/{date}_{version}_perf.json".format(date=date.today().strftime('%Y-%m-%d'), version=VERSION)
|
||||
eprint('Storing results in {}'.format(name))
|
||||
with open(name, 'w') as fp:
|
||||
json.dump(results, fp, indent=2)
|
||||
eprint("done.")
|
|
@ -1,61 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from common import EC2Environment, CREATE
|
||||
import atexit, argparse, os
|
||||
|
||||
REGION = "eu-central-1"
|
||||
|
||||
VERSION = "2.1.0"
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Create a test setup')
|
||||
parser.add_argument('--instancetype', default='t3a.nano', help='EC2 instance type')
|
||||
parser.add_argument('--version', default=VERSION, help='VpnCloud version to use')
|
||||
parser.add_argument('--count', '-c', dest="count", type=int, default=2, help='Number of instance to create')
|
||||
parser.add_argument('--cluster', action="store_true", help='Cluster instances to get reliable throughput')
|
||||
parser.add_argument('--subnet', help='AWS subnet id to use (empty = create new one)')
|
||||
parser.add_argument('--keyname', help='Name of AWS keypair to use (empty = create new one)')
|
||||
parser.add_argument('--keyfile', default="key.pem", help='Path of the private key file')
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
privatekey = None
|
||||
if args.keyname:
|
||||
with open(args.keyfile, 'r') as fp:
|
||||
privatekey = fp.read()
|
||||
|
||||
opts = {}
|
||||
if os.path.exists(args.version):
|
||||
opts["vpncloud_file"] = args.version
|
||||
opts["vpncloud_version"] = None
|
||||
else:
|
||||
opts["vpncloud_version"] = args.version
|
||||
|
||||
setup = EC2Environment(
|
||||
region = REGION,
|
||||
node_count = args.count,
|
||||
instance_type = args.instancetype,
|
||||
cluster_nodes = args.cluster,
|
||||
subnet = args.subnet or CREATE,
|
||||
keyname = args.keyname or CREATE,
|
||||
privatekey = privatekey,
|
||||
**opts
|
||||
)
|
||||
|
||||
if not args.keyname:
|
||||
assert not os.path.exists(args.keyfile)
|
||||
with open(args.keyfile, 'x') as fp:
|
||||
fp.write(setup.privatekey)
|
||||
os.chmod(args.keyfile, 0o400)
|
||||
print("SSH private key written to {}".format(args.keyfile))
|
||||
atexit.register(lambda : os.remove(args.keyfile))
|
||||
print()
|
||||
|
||||
print("Nodes:")
|
||||
for node in setup.nodes:
|
||||
print("\t {}@{}\tprivate: {}".format(setup.username, node.public_ip, node.private_ip))
|
||||
print()
|
||||
|
||||
print("Press ENTER to shut down")
|
||||
input()
|
|
@ -0,0 +1,11 @@
|
|||
vpncloud/debian/files
|
||||
vpncloud/debian/vpncloud
|
||||
vpncloud/vpncloud
|
||||
vpncloud-nocrypto/debian/vpncloud*
|
||||
vpncloud-nocrypto/debian/files
|
||||
vpncloud-nocrypto/vpncloud
|
||||
*.deb
|
||||
*.build
|
||||
*.changes
|
||||
*.debhelper*
|
||||
*.substvars
|
|
@ -0,0 +1,25 @@
|
|||
PACKAGE=vpncloud
|
||||
DEPENDENCIES=debhelper devscripts
|
||||
|
||||
.PHONY: default
|
||||
default: clean build
|
||||
|
||||
.PHONY: build
|
||||
build: $(PACKAGE)_*.deb
|
||||
$(PACKAGE)_*.deb: $(PACKAGE)/vpncloud.1.ronn $(PACKAGE)/vpncloud
|
||||
(cd $(PACKAGE); make clean; debuild -b -us -uc; cd ..)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
(cd $(PACKAGE); debuild clean; cd ..)
|
||||
rm -rf $(PACKAGE)_*
|
||||
rm -f ../target/release/vpncloud
|
||||
|
||||
$(PACKAGE)/vpncloud.1.ronn: ../vpncloud.md
|
||||
cp ../vpncloud.md $(PACKAGE)/vpncloud.1.ronn
|
||||
|
||||
$(PACKAGE)/vpncloud: ../target/release/vpncloud
|
||||
cp ../target/release/vpncloud $(PACKAGE)/vpncloud
|
||||
|
||||
../target/release/vpncloud: ../src/*.rs ../Cargo.toml
|
||||
(cd ..; cargo build --release)
|
|
@ -0,0 +1,12 @@
|
|||
build: vpncloud.1
|
||||
|
||||
vpncloud.1: vpncloud.1.ronn
|
||||
ronn -r vpncloud.1.ronn
|
||||
|
||||
install:
|
||||
install -d $(DESTDIR)/etc/vpncloud
|
||||
install -m 600 default.net $(DESTDIR)/etc/vpncloud/default.net
|
||||
install -d $(DESTDIR)/var/log
|
||||
install -d $(DESTDIR)/run
|
||||
install -d $(DESTDIR)/usr/bin
|
||||
install -m 755 vpncloud $(DESTDIR)/usr/bin/vpncloud
|
|
@ -0,0 +1,48 @@
|
|||
vpncloud (0.4.1) stable; urgency=medium
|
||||
|
||||
* [changed] Logging more verbosely
|
||||
* [fixed] Removing NULL-bytes from interface name
|
||||
* [fixed] Supporting hostnames as peers
|
||||
* [fixed] No longer encrypting multiple times
|
||||
* [fixed] Properly decoding protocol header when sending
|
||||
* [fixed] Corrected size of read data
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 22 Dec 2015 22:51:30 +0100
|
||||
|
||||
vpncloud (0.4) stable; urgency=medium
|
||||
|
||||
* [added] Init script
|
||||
* [changed] Removed last payload memcopy
|
||||
* [changed] Using RNG to select peers for peers list exchange
|
||||
* [changed] Updated dependency versions
|
||||
* [changed] Updated documentation
|
||||
* [fixed] Printing errors instead of panics in some cases
|
||||
* [fixed] Build script for Debian packages
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 22 Dec 2015 19:23:26 +0100
|
||||
|
||||
vpncloud (0.3.1) stable; urgency=medium
|
||||
|
||||
* Preventing nodes from connecting to themselves
|
||||
* Flushing TAP/TUN device after writing to it
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Thu, 03 Dec 2015 21:53:43 +0100
|
||||
|
||||
vpncloud (0.3.0) stable; urgency=medium
|
||||
|
||||
* Inluding libsodium-1.0.7
|
||||
* Support for AES256
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 01 Dec 2015 16:12:16 +0100
|
||||
|
||||
vpncloud (0.2.0) stable; urgency=medium
|
||||
|
||||
* More stable release
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Thu, 26 Nov 2015 17:41:40 +0100
|
||||
|
||||
vpncloud (0.1.0) stable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Dennis Schwerdel <schwerdel@informatik.uni-kl.de> Tue, 24 Nov 2015 09:31:47 +0100
|
|
@ -0,0 +1 @@
|
|||
7
|
|
@ -0,0 +1,11 @@
|
|||
Source: vpncloud
|
||||
Section: misc
|
||||
Priority: extra
|
||||
Maintainer: Dennis Schwerdel <schwerdel@informatik.uni-kl.de>
|
||||
Build-Depends: debhelper (>= 7), ruby-ronn, rust-stable
|
||||
Standards-Version: 3.8.3
|
||||
|
||||
Package: vpncloud
|
||||
Architecture: amd64
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: Peer-to-peer VPN
|
|
@ -0,0 +1,31 @@
|
|||
Upstream Author:
|
||||
|
||||
Dennis Schwerdel <schwerdel@informatik.uni-kl.de>
|
||||
|
||||
Copyright:
|
||||
|
||||
Copyright (C) 2015 Dennis Schwerdel, University of Kaiserslautern
|
||||
|
||||
License:
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||
|
||||
The Debian packaging is:
|
||||
|
||||
Copyright (C) 2015 Dennis Schwerdel, University of Kaiserslautern
|
||||
|
||||
and is licensed under the GPL version 3, see above.
|
|
@ -0,0 +1 @@
|
|||
vpncloud.1
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh $@
|
|
@ -0,0 +1 @@
|
|||
NETWORKS="default"
|
|
@ -0,0 +1,161 @@
|
|||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: vpncloud
|
||||
# Required-Start: $network $remote_fs
|
||||
# Required-Stop: $remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: VpnCloud
|
||||
# Description: VpnCloud - Peer-to-Peer VPN
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Dennis Schwerdel <schwerdel@informatik.uni-kl.de>
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="VpnCloud" # Introduce a short description here
|
||||
NAME=vpncloud # Introduce the short server's name here
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
NETCONFIGS=/etc/vpncloud
|
||||
|
||||
# default settings
|
||||
USER=root
|
||||
GROUP=root
|
||||
UMASK=022
|
||||
|
||||
NETWORKS="default"
|
||||
|
||||
DAEMON=$(which $NAME)
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x $DAEMON ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
do_status() {
|
||||
for net in $NETWORKS; do
|
||||
if start-stop-daemon --status --pidfile /run/$NAME-$net.pid --name $NAME; then
|
||||
echo -e "\t$net"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
do_start() {
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
for net in $NETWORKS; do
|
||||
ENABLED=0
|
||||
unset DEVICE LISTEN TYPE MODE SHARED_KEY CRYPTO IFUP IFDOWN NETWORK_ID PEER_TIMEOUT DST_TIMEOUT PEERS SUBNETS
|
||||
[ -f "$NETCONFIGS/$net.net" ] && . $NETCONFIGS/$net.net
|
||||
if [ $ENABLED -eq 1 ]; then
|
||||
PARAMS=""
|
||||
[ -z "$DEVICE" ] || PARAMS="$PARAMS --device $DEVICE"
|
||||
[ -z "$LISTEN" ] || PARAMS="$PARAMS --listen $LISTEN"
|
||||
[ -z "$TYPE" ] || PARAMS="$PARAMS --type $TYPE"
|
||||
[ -z "$MODE" ] || PARAMS="$PARAMS --mode $MODE"
|
||||
[ -z "$SHARED_KEY" ] || PARAMS="$PARAMS --shared-key '$SHARED_KEY'"
|
||||
[ -z "$CRYPTO" ] || PARAMS="$PARAMS --crypto $CRYPTO"
|
||||
[ -z "$IFUP" ] || PARAMS="$PARAMS --ifup '$IFUP'"
|
||||
[ -z "$IFDOWN" ] || PARAMS="$PARAMS --ifdown '$IFDOWN'"
|
||||
[ -z "$NETWORK_ID" ] || PARAMS="$PARAMS --network-id $NETWORK_ID"
|
||||
[ -z "$PEER_TIMEOUT" ] || PARAMS="$PARAMS --peer-timeout $PEER_TIMEOUT"
|
||||
[ -z "$DST_TIMEOUT" ] || PARAMS="$PARAMS --peer-timeout $DST_TIMEOUT"
|
||||
for peer in $PEERS; do
|
||||
PARAMS="$PARAMS --connect $peer"
|
||||
done
|
||||
for subnet in $SUBNETS; do
|
||||
PARAMS="$PARAMS --subnet $subnet"
|
||||
done
|
||||
start-stop-daemon --start --pidfile /run/$NAME-$net.pid --make-pidfile --name $NAME --background --startas /bin/sh -- -c "exec $DAEMON $PARAMS >/var/log/vpncloud-$net.log 2>&1"
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
do_stop() {
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
for net in $NETWORKS; do
|
||||
start-stop-daemon --stop --quiet --pidfile /run/$NAME-$net.pid --name $NAME --retry 60
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
log_begin_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
log_begin_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0; exit 0 ;;
|
||||
2) log_end_msg 1; exit 2 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
do_status
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_begin_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
|
@ -0,0 +1,20 @@
|
|||
ENABLED=0
|
||||
|
||||
LISTEN=''
|
||||
PEERS=''
|
||||
|
||||
PEER_TIMEOUT=''
|
||||
DST_TIMEOUT=''
|
||||
|
||||
NETWORK_ID=''
|
||||
SHARED_KEY=''
|
||||
CRYPTO=''
|
||||
|
||||
DEVICE=''
|
||||
TYPE=''
|
||||
|
||||
MODE=''
|
||||
SUBNETS=''
|
||||
|
||||
IFUP=''
|
||||
IFDOWN=''
|
|
@ -0,0 +1 @@
|
|||
Subproject commit a84ae0170c73641d9581780549a51abadbbb26eb
|
209
maskfile.md
209
maskfile.md
|
@ -1,209 +0,0 @@
|
|||
# Commands
|
||||
|
||||
Needs [mask](https://github.com/jacobdeichert/mask) to run.
|
||||
|
||||
|
||||
## install-tools
|
||||
|
||||
> Install tools.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
apt-get install -y asciidoctor
|
||||
cargo install cargo-binstall
|
||||
cargo binstall cross cargo-deb cargo-generate-rpm
|
||||
UPX_VERSION=$(grep -e '^upx_version =' Cargo.toml | sed -e 's/upx_version = "\(.*\)"/\1/')
|
||||
curl https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-amd64_linux.tar.xz -Lf | tar -xJ --strip-components=1 -C /usr/local/bin
|
||||
```
|
||||
|
||||
## manpage
|
||||
|
||||
> Generate manpage.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
echo >&2 "Generating manpage"
|
||||
if [ ! -f target/vpncloud.1.gz -o vpncloud.adoc -nt target/vpncloud.1.gz ]; then
|
||||
asciidoctor -b manpage -o target/vpncloud.1 vpncloud.adoc
|
||||
gzip -f target/vpncloud.1
|
||||
fi
|
||||
```
|
||||
|
||||
## build-packages-cross (target) (target_name) (target_name_rpm)
|
||||
|
||||
> Build the project packages for a given target.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
TARGET=$target
|
||||
TARGET_DIR=target/$target_name
|
||||
|
||||
# compile
|
||||
echo >&2 "Compiling for $target_name"
|
||||
cross build --release --target $TARGET --target-dir $TARGET_DIR
|
||||
mkdir -p target/$TARGET/release
|
||||
cp $TARGET_DIR/$TARGET/release/vpncloud target/$TARGET/release/
|
||||
|
||||
# build deb
|
||||
echo >&2 "Building deb package"
|
||||
cargo deb --no-build --no-strip --target $TARGET
|
||||
mv target/$TARGET/debian/vpncloud_${VERSION}-1_$target_name.deb dist/vpncloud_${VERSION}_$target_name.deb
|
||||
|
||||
# build rpm
|
||||
if [ -n "$target_name_rpm" ]; then
|
||||
echo >&2 "Building rpm package"
|
||||
cargo generate-rpm --target $TARGET --target-dir $TARGET_DIR
|
||||
mv $TARGET_DIR/$TARGET/generate-rpm/vpncloud-${VERSION}-1.$target_name_rpm.rpm dist/vpncloud_${VERSION}-1.$target_name_rpm.rpm
|
||||
fi
|
||||
```
|
||||
|
||||
## build-amd64-packages
|
||||
|
||||
```sh
|
||||
$MASK build-packages-cross x86_64-unknown-linux-gnu amd64 x86_64
|
||||
```
|
||||
|
||||
## build-i386-packages
|
||||
|
||||
```sh
|
||||
$MASK build-packages-cross i686-unknown-linux-gnu i386 i686
|
||||
```
|
||||
|
||||
## build-arm64-packages
|
||||
|
||||
```sh
|
||||
$MASK build-packages-cross aarch64-unknown-linux-gnu arm64 aarch64
|
||||
```
|
||||
|
||||
## build-armhf-packages
|
||||
|
||||
```sh
|
||||
$MASK build-packages-cross armv7-unknown-linux-gnueabihf armhf ""
|
||||
```
|
||||
|
||||
## build-armel-packages
|
||||
|
||||
```sh
|
||||
$MASK build-packages-cross armv5te-unknown-linux-gnueabi armel ""
|
||||
```
|
||||
|
||||
|
||||
|
||||
## build-static-cross (target) (target_name)
|
||||
|
||||
> Build the project statically for a given target.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
TARGET=$target
|
||||
TARGET_DIR=target/$target_name-musl
|
||||
BIN=$TARGET_DIR/$TARGET/release/vpncloud
|
||||
|
||||
echo >&2 "Compiling for $target_name musl"
|
||||
cross build --release --features installer --target $TARGET --target-dir $TARGET_DIR
|
||||
upx --lzma $BIN
|
||||
cp $BIN dist/vpncloud_${VERSION}_static_$target_name
|
||||
```
|
||||
|
||||
## build-amd64-static
|
||||
|
||||
```sh
|
||||
$MASK build-static-cross x86_64-unknown-linux-musl amd64
|
||||
```
|
||||
|
||||
|
||||
## build-i386-static
|
||||
|
||||
```sh
|
||||
$MASK build-static-cross i686-unknown-linux-musl i386
|
||||
```
|
||||
|
||||
|
||||
## build-arm64-static
|
||||
|
||||
```sh
|
||||
$MASK build-static-cross aarch64-unknown-linux-musl arm64
|
||||
```
|
||||
|
||||
## build-armhf-static
|
||||
|
||||
```sh
|
||||
$MASK build-static-cross armv7-unknown-linux-musleabihf armhf
|
||||
```
|
||||
|
||||
## build-armel-static
|
||||
|
||||
```sh
|
||||
$MASK build-static-cross armv5te-unknown-linux-musleabi armel
|
||||
```
|
||||
|
||||
|
||||
## build
|
||||
|
||||
> Build the project for all architectures.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
$MASK manpage
|
||||
$MASK build-amd64-packages
|
||||
$MASK build-amd64-static
|
||||
$MASK build-i386-packages
|
||||
$MASK build-i386-static
|
||||
$MASK build-arm64-packages
|
||||
$MASK build-arm64-static
|
||||
$MASK build-armhf-packages
|
||||
$MASK build-armhf-static
|
||||
$MASK build-armel-packages
|
||||
$MASK build-armel-static
|
||||
```
|
||||
|
||||
## sign
|
||||
|
||||
> Sign the packages.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
cd dist
|
||||
sha256sum vpncloud_${VERSION}_static_* vpncloud_${VERSION}*.rpm vpncloud_${VERSION}*.deb > vpncloud_${VERSION}_SHA256SUMS.txt
|
||||
gpg --armor --output vpncloud_${VERSION}_SHA256SUMS.txt.asc --detach-sig vpncloud_${VERSION}_SHA256SUMS.txt
|
||||
```
|
||||
|
||||
## test
|
||||
|
||||
> Test the project.
|
||||
|
||||
```sh
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
## release
|
||||
|
||||
> Release the project.
|
||||
|
||||
```sh
|
||||
set -e
|
||||
|
||||
$MASK test
|
||||
nano Cargo.toml
|
||||
VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/')
|
||||
nano CHANGELOG.md
|
||||
nano assets/changelog.txt
|
||||
$MASK build
|
||||
$MASK sign
|
||||
git commit -a
|
||||
cargo publish
|
||||
git tag v$VERSION
|
||||
git push --tags
|
||||
```
|
||||
|
||||
|
||||
## count
|
||||
|
||||
> Count the lines of code.
|
||||
|
||||
```sh
|
||||
tokei
|
||||
```
|
|
@ -0,0 +1,85 @@
|
|||
Performance Tests
|
||||
-----------------
|
||||
|
||||
### Test setup
|
||||
|
||||
Sender node:
|
||||
* Intel(R) Core(TM) i5-2540M CPU @ 2.60GHz
|
||||
* 8 GiB Ram
|
||||
* Intel 82579LM Gigabit Network
|
||||
* Ubuntu 14.04 (Kernel 3.13.0-65-generic)
|
||||
|
||||
Receiver node:
|
||||
* Intel(R) Core(TM) i5-3450 CPU @ 3.10GHz
|
||||
* 16 GiB Ram
|
||||
* Realtek RTL8111/8168/8411 Gigabit Network
|
||||
* Ubuntu 14.04 (Kernel 3.13.0-63-generic)
|
||||
|
||||
VpnCloud version: `VpnCloud v0.3.1, protocol version 1, libsodium 1.0.7 (AES256: true)`
|
||||
|
||||
The sender runs the following command:
|
||||
|
||||
```
|
||||
$> ./vpncloud -t tap -l SENDER:3210 -c RECEIVER:3210 \
|
||||
--ifup 'ifconfig $IFNAME 10.2.1.1/24 mtu 1400 up' &
|
||||
```
|
||||
|
||||
and the receiver runs:
|
||||
|
||||
```
|
||||
$> ./vpncloud -t tap -l RECEIVER:3210 -c SENDER:3210 \
|
||||
--ifup 'ifconfig $IFNAME 10.2.1.2/24 mtu 1400 up' &
|
||||
$> iperf -s &
|
||||
$> top
|
||||
```
|
||||
|
||||
For encrypted tests, `--shared-key test --crypto METHOD` is appended.
|
||||
|
||||
|
||||
### Throughput
|
||||
|
||||
The throughput is measured with the following command:
|
||||
|
||||
```
|
||||
$> iperf -c DST -t 60
|
||||
```
|
||||
|
||||
The test is run in 3 steps:
|
||||
* Native throughput without VpnCloud (`DST` is the native address of the receiver)
|
||||
* Throughput via VpnCloud (`DST` is `10.2.1.2`)
|
||||
* Encrypted throughput via VpnCloud (`DST` is `10.2.1.2`)
|
||||
|
||||
|
||||
| Throughput test | Bandwidth | CPU usage (one core) |
|
||||
| ----------------------------- | ------------- | -------------------- |
|
||||
| Without VpnCloud | 926 Mbits/sec | - |
|
||||
| Unencrypted VpnCloud | 875 Mbits/sec | 80% / 95% |
|
||||
| Encrypted VpnCloud (ChaCha20) | 799 Mbits/sec | 100% |
|
||||
| Encrypted VpnCloud (AES256) | 837 Mbits/sec | 90% / 100% |
|
||||
|
||||
|
||||
### Latency
|
||||
|
||||
The latency is measured with the following command:
|
||||
```
|
||||
$> ping DST -c 100000 -i 0.001 -s SIZE -U -q
|
||||
```
|
||||
|
||||
For all the test, the best average RTT out of 3 runs is selected. The latency is
|
||||
assumed to be half of the RTT.
|
||||
|
||||
|
||||
| Payload size | 100 bytes | 500 bytes | 1000 bytes |
|
||||
| ----------------------------- | --------------- | --------------- | --------------- |
|
||||
| Without VpnCloud | 158 µs | 164 µs | 171 µs |
|
||||
| Unencrypted VpnCloud | 208 µs (+50 µs) | 225 µs (+61 µs) | 236 µs (+65 µs) |
|
||||
| Encrypted VpnCloud (ChaCha20) | 229 µs (+21 µs) | 242 µs (+17 µs) | 259 µs (+23 µs) |
|
||||
| Encrypted VpnCloud (AES256) | 223 µs (+15 µs) | 232 µs ( +7 µs) | 249 µs (+13 µs) |
|
||||
|
||||
|
||||
### Conclusion
|
||||
|
||||
* VpnCloud achieves over 850 MBit/s with default MTU settings.
|
||||
* In encrypted mode, VpnCloud reaches over 800 MBit/s with default MTU settings.
|
||||
* VpnCloud adds about 70µs to the latency.
|
||||
* Encryption adds an additional latency up to 20µs.
|
19
rustfmt.toml
19
rustfmt.toml
|
@ -1,19 +0,0 @@
|
|||
use_small_heuristics = "Max"
|
||||
comment_width = 120
|
||||
fn_args_layout = "Compressed"
|
||||
where_single_line = true
|
||||
merge_imports = true
|
||||
max_width = 120
|
||||
force_multiline_blocks = true
|
||||
normalize_comments = true
|
||||
trailing_comma = "Never"
|
||||
trailing_semicolon = false
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
wrap_comments = true
|
||||
overflow_delimited_expr = true
|
||||
blank_lines_upper_bound = 2
|
||||
normalize_doc_attributes = true
|
||||
inline_attribute_width = 50
|
||||
edition = "2018"
|
||||
reorder_impl_items = true
|
460
src/beacon.rs
460
src/beacon.rs
|
@ -1,460 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use ring::digest;
|
||||
|
||||
use std::{
|
||||
fs::{self, File, Permissions},
|
||||
io::{self, Read, Write},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::Wrapping,
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use super::util::{from_base62, to_base62, Encoder, TimeSource};
|
||||
use smallvec::SmallVec;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
|
||||
const TYPE_BEGIN: u8 = 0;
|
||||
const TYPE_END: u8 = 1;
|
||||
const TYPE_DATA: u8 = 2;
|
||||
const TYPE_SEED: u8 = 3;
|
||||
|
||||
fn base_62_sanitize(data: &str) -> String {
|
||||
data.chars().filter(|c| c.is_ascii_alphanumeric()).collect()
|
||||
}
|
||||
|
||||
fn sha512(data: &[u8]) -> SmallVec<[u8; 64]> {
|
||||
digest::digest(&digest::SHA512, data).as_ref().into()
|
||||
}
|
||||
|
||||
struct FutureResult<T> {
|
||||
has_result: AtomicBool,
|
||||
result: Mutex<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BeaconSerializer<TS> {
|
||||
shared_key: Vec<u8>,
|
||||
future_peers: Arc<FutureResult<Vec<SocketAddr>>>,
|
||||
_dummy_ts: PhantomData<TS>,
|
||||
}
|
||||
|
||||
impl<TS: TimeSource> BeaconSerializer<TS> {
|
||||
pub fn new(shared_key: &[u8]) -> Self {
|
||||
Self {
|
||||
shared_key: shared_key.to_owned(),
|
||||
future_peers: Arc::new(FutureResult { has_result: AtomicBool::new(false), result: Mutex::new(Vec::new()) }),
|
||||
_dummy_ts: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn now_hour_16() -> u16 {
|
||||
((TS::now() / 3600) & 0xffff) as u16
|
||||
}
|
||||
|
||||
fn get_keystream(&self, type_: u8, seed: u8, iter: u8) -> SmallVec<[u8; 64]> {
|
||||
let mut data = SmallVec::<[u8; 128]>::new();
|
||||
data.extend_from_slice(&[type_, seed, iter]);
|
||||
data.extend_from_slice(&self.shared_key);
|
||||
sha512(&data)
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
fn mask_with_keystream(&self, data: &mut [u8], type_: u8, seed: u8) {
|
||||
let mut iter = 0;
|
||||
let mut mask = self.get_keystream(type_, seed, iter);
|
||||
let mut pos = 0;
|
||||
for i in 0..data.len() {
|
||||
data[i] ^= mask[pos];
|
||||
pos += 1;
|
||||
if pos == 16 {
|
||||
pos = 0;
|
||||
iter += 1;
|
||||
mask = self.get_keystream(type_, seed, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn begin(&self) -> String {
|
||||
to_base62(&self.get_keystream(TYPE_BEGIN, 0, 0))[0..5].to_string()
|
||||
}
|
||||
|
||||
fn end(&self) -> String {
|
||||
to_base62(&self.get_keystream(TYPE_END, 0, 0))[0..5].to_string()
|
||||
}
|
||||
|
||||
fn encrypt_data(&self, data: &mut Vec<u8>) {
|
||||
// Note: the 1 byte seed is only meant to protect from random changes,
|
||||
// not malicious ones. For full protection, at least 8 bytes (~12
|
||||
// characters) would be needed.
|
||||
let seed = sha512(data as &[u8])[0];
|
||||
self.mask_with_keystream(data as &mut [u8], TYPE_DATA, seed);
|
||||
data.push(seed ^ self.get_keystream(TYPE_SEED, 0, 0)[0]);
|
||||
}
|
||||
|
||||
fn decrypt_data(&self, data: &mut Vec<u8>) -> bool {
|
||||
if data.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let seed = data.pop().unwrap() ^ self.get_keystream(TYPE_SEED, 0, 0)[0];
|
||||
self.mask_with_keystream(data as &mut [u8], TYPE_DATA, seed);
|
||||
seed == sha512(data as &[u8])[0]
|
||||
}
|
||||
|
||||
fn peerlist_encode(&self, peers: &[SocketAddr]) -> String {
|
||||
let mut data = Vec::new();
|
||||
// Add timestamp
|
||||
data.extend_from_slice(&Self::now_hour_16().to_be_bytes());
|
||||
// Split addresses into v4 and v6
|
||||
let mut v4addrs = SmallVec::<[SocketAddrV4; 256]>::new();
|
||||
let mut v6addrs = SmallVec::<[SocketAddrV6; 256]>::new();
|
||||
for p in peers {
|
||||
match *p {
|
||||
SocketAddr::V4(addr) => v4addrs.push(addr),
|
||||
SocketAddr::V6(addr) => v6addrs.push(addr),
|
||||
}
|
||||
}
|
||||
// Add count of v4 addresses
|
||||
data.push(v4addrs.len() as u8);
|
||||
// Add v4 addresses
|
||||
for addr in v4addrs {
|
||||
let mut dat = [0u8; 6];
|
||||
dat[0..4].copy_from_slice(&addr.ip().octets());
|
||||
Encoder::write_u16(addr.port(), &mut dat[4..]);
|
||||
data.extend_from_slice(&dat);
|
||||
}
|
||||
// Add v6 addresses
|
||||
for addr in v6addrs {
|
||||
let mut dat = [0u8; 18];
|
||||
let ip = addr.ip().segments();
|
||||
Encoder::write_u16(ip[0], &mut dat[0..]);
|
||||
Encoder::write_u16(ip[1], &mut dat[2..]);
|
||||
Encoder::write_u16(ip[2], &mut dat[4..]);
|
||||
Encoder::write_u16(ip[3], &mut dat[6..]);
|
||||
Encoder::write_u16(ip[4], &mut dat[8..]);
|
||||
Encoder::write_u16(ip[5], &mut dat[10..]);
|
||||
Encoder::write_u16(ip[6], &mut dat[12..]);
|
||||
Encoder::write_u16(ip[7], &mut dat[14..]);
|
||||
Encoder::write_u16(addr.port(), &mut dat[16..]);
|
||||
data.extend_from_slice(&dat);
|
||||
}
|
||||
self.encrypt_data(&mut data);
|
||||
to_base62(&data)
|
||||
}
|
||||
|
||||
fn peerlist_decode(&self, data: &str, ttl_hours: Option<u16>) -> Vec<SocketAddr> {
|
||||
let mut data = from_base62(data).expect("Invalid input");
|
||||
let mut peers = Vec::new();
|
||||
let mut pos = 0;
|
||||
if data.len() < 4 {
|
||||
return peers;
|
||||
}
|
||||
if !self.decrypt_data(&mut data) {
|
||||
return peers;
|
||||
}
|
||||
let then = Wrapping(Encoder::read_u16(&data[pos..=pos + 1]));
|
||||
if let Some(ttl) = ttl_hours {
|
||||
let now = Wrapping(Self::now_hour_16());
|
||||
if now - then > Wrapping(ttl) && then - now > Wrapping(ttl) {
|
||||
return peers;
|
||||
}
|
||||
}
|
||||
pos += 2;
|
||||
let v4count = data[pos] as usize;
|
||||
pos += 1;
|
||||
if v4count * 6 > data.len() - pos || (data.len() - pos - v4count * 6) % 18 > 0 {
|
||||
return peers;
|
||||
}
|
||||
for _ in 0..v4count {
|
||||
assert!(data.len() >= pos + 6);
|
||||
let dat = &data[pos..pos + 6];
|
||||
pos += 6;
|
||||
let port = Encoder::read_u16(&dat[4..]);
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(dat[0], dat[1], dat[2], dat[3]), port));
|
||||
peers.push(addr);
|
||||
}
|
||||
let v6count = (data.len() - pos) / 18;
|
||||
for _ in 0..v6count {
|
||||
assert!(data.len() >= pos + 18);
|
||||
let dat = &data[pos..pos + 18];
|
||||
pos += 18;
|
||||
let mut ip = [0u16; 8];
|
||||
for i in 0..8 {
|
||||
ip[i] = Encoder::read_u16(&dat[i * 2..i * 2 + 2]);
|
||||
}
|
||||
let port = Encoder::read_u16(&dat[16..]);
|
||||
let addr = SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::new(ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]),
|
||||
port,
|
||||
0,
|
||||
0,
|
||||
));
|
||||
peers.push(addr);
|
||||
}
|
||||
peers
|
||||
}
|
||||
|
||||
pub fn encode(&self, peers: &[SocketAddr]) -> String {
|
||||
format!("{}{}{}", self.begin(), self.peerlist_encode(peers), self.end())
|
||||
}
|
||||
|
||||
pub fn write_to_file<P: AsRef<Path>>(&self, peers: &[SocketAddr], path: P) -> Result<(), io::Error> {
|
||||
let beacon = self.encode(peers);
|
||||
debug!("Beacon: {}", beacon);
|
||||
let path = path.as_ref();
|
||||
if path.exists() {
|
||||
fs::remove_file(path)?
|
||||
}
|
||||
let mut f = File::create(path)?;
|
||||
writeln!(&mut f, "{}", beacon)?;
|
||||
fs::set_permissions(path, Permissions::from_mode(0o444))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_to_cmd(&self, peers: &[SocketAddr], cmd: &str) -> Result<(), io::Error> {
|
||||
let begin = self.begin();
|
||||
let data = self.peerlist_encode(peers);
|
||||
let end = self.end();
|
||||
let beacon = format!("{}{}{}", begin, data, end);
|
||||
debug!("Calling beacon command: {}", cmd);
|
||||
let process = Command::new("sh")
|
||||
.args(["-c", cmd])
|
||||
.env("begin", begin)
|
||||
.env("data", data)
|
||||
.env("end", end)
|
||||
.env("beacon", beacon)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
thread::spawn(move || {
|
||||
let output = process.wait_with_output().expect("Failed to wait on child");
|
||||
if !output.status.success() {
|
||||
error!("Beacon command failed: {}", String::from_utf8_lossy(&output.stderr));
|
||||
} else {
|
||||
debug!("Beacon command succeeded");
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode(&self, data: &str, ttl_hours: Option<u16>) -> Vec<SocketAddr> {
|
||||
let data = base_62_sanitize(data);
|
||||
let mut peers = Vec::new();
|
||||
let begin = self.begin();
|
||||
let end = self.end();
|
||||
let mut pos = 0;
|
||||
while let Some(found) = data[pos..].find(&begin) {
|
||||
pos += found;
|
||||
let start_pos = pos + begin.len();
|
||||
if let Some(found) = data[pos..].find(&end) {
|
||||
let end_pos = pos + found;
|
||||
peers.append(&mut self.peerlist_decode(&data[start_pos..end_pos], ttl_hours));
|
||||
pos = start_pos
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
peers
|
||||
}
|
||||
|
||||
pub fn read_from_file<P: AsRef<Path>>(
|
||||
&self, path: P, ttl_hours: Option<u16>,
|
||||
) -> Result<Vec<SocketAddr>, io::Error> {
|
||||
let mut f = File::open(&path)?;
|
||||
let mut contents = String::new();
|
||||
f.read_to_string(&mut contents)?;
|
||||
Ok(self.decode(&contents, ttl_hours))
|
||||
}
|
||||
|
||||
pub fn read_from_cmd(&self, cmd: &str, ttl_hours: Option<u16>) -> Result<(), io::Error> {
|
||||
let begin = self.begin();
|
||||
let end = self.end();
|
||||
debug!("Calling beacon command: {}", cmd);
|
||||
let process = Command::new("sh")
|
||||
.args(["-c", cmd])
|
||||
.env("begin", begin)
|
||||
.env("end", end)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
let this = self.clone();
|
||||
thread::spawn(move || {
|
||||
let output = process.wait_with_output().expect("Failed to wait on child");
|
||||
if output.status.success() {
|
||||
let data = String::from_utf8_lossy(&output.stdout);
|
||||
let mut peers = this.decode(&data, ttl_hours);
|
||||
debug!("Beacon command succeeded with {} peers", peers.len());
|
||||
mem::swap(&mut peers, &mut this.future_peers.result.lock().expect("Lock poisoned"));
|
||||
this.future_peers.has_result.store(true, Ordering::Relaxed);
|
||||
} else {
|
||||
error!("Beacon command failed: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_cmd_results(&self) -> Option<Vec<SocketAddr>> {
|
||||
if self.future_peers.has_result.load(Ordering::Relaxed) {
|
||||
let mut peers = Vec::new();
|
||||
mem::swap(&mut peers, &mut self.future_peers.result.lock().expect("Lock poisoned"));
|
||||
self.future_peers.has_result.store(false, Ordering::Relaxed);
|
||||
Some(peers)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::util::MockTimeSource;
|
||||
#[cfg(test)]
|
||||
use std::str::FromStr;
|
||||
#[cfg(test)]
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn encode() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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()];
|
||||
assert_eq!("WsHI31EWDMBYxvITiILIrm2k9gEik22E", ser.encode(&peers));
|
||||
peers.push(SocketAddr::from_str("[::1]:5678").unwrap());
|
||||
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()];
|
||||
assert_eq!("WsHI32gm9eMSHP3Lm1GXcdP7rD3ik22E", ser.encode(&peers));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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()];
|
||||
assert_eq!(format!("{:?}", peers), format!("{:?}", ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None)));
|
||||
peers.push(SocketAddr::from_str("[::1]:5678").unwrap());
|
||||
assert_eq!(
|
||||
format!("{:?}", peers),
|
||||
format!("{:?}", ser.decode("WsHI3GXKaXCveo6uejmZizZ72kR6Y0L9T7h49TXONp1ugfKvvvEik22E", None))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_split() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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()];
|
||||
assert_eq!(
|
||||
format!("{:?}", peers),
|
||||
format!("{:?}", ser.decode("WsHI3-1E.WD:MB Yx\tvI\nTi(IL)Ir[m2]k9ügEäik22E", None))
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", peers),
|
||||
format!("{:?}", ser.decode("W -, \nsH--I31EWDMBYxvITiILIrm2k9gEi(k)2ÖÄÜ\n2E", None))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_offset() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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()];
|
||||
assert_eq!(
|
||||
format!("{:?}", peers),
|
||||
format!("{:?}", ser.decode("Hello World: WsHI31EWDMBYxvITiILIrm2k9gEik22E! End of the World", None))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_multiple() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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()];
|
||||
assert_eq!(
|
||||
format!("{:?}", peers),
|
||||
format!("{:?}", ser.decode("WsHI31HVpqxFNMNSPrvik22E WsHI34yOBcZIulKdtn2ik22E", None))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_ttl() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None).len());
|
||||
MockTimeSource::set_time(2100 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None).len());
|
||||
MockTimeSource::set_time(2005 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None).len());
|
||||
MockTimeSource::set_time(1995 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", None).len());
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", Some(24)).len());
|
||||
MockTimeSource::set_time(1995 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", Some(24)).len());
|
||||
MockTimeSource::set_time(2005 * 3600);
|
||||
assert_eq!(2, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", Some(24)).len());
|
||||
MockTimeSource::set_time(2100 * 3600);
|
||||
assert_eq!(0, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", Some(24)).len());
|
||||
MockTimeSource::set_time(1900 * 3600);
|
||||
assert_eq!(0, ser.decode("WsHI31EWDMBYxvITiILIrm2k9gEik22E", Some(24)).len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
let ser = BeaconSerializer::<MockTimeSource>::new(b"mysecretkey");
|
||||
assert_eq!(0, ser.decode("", None).len());
|
||||
assert_eq!(0, ser.decode("WsHI3ik22E", None).len());
|
||||
assert_eq!(0, ser.decode("WsHI3--", None).len());
|
||||
assert_eq!(0, ser.decode("--ik22E", None).len());
|
||||
assert_eq!(0, ser.decode("WsHI32EWDMBYxvITiILIrm2k9gEik22E", None).len());
|
||||
assert_eq!(2, ser.decode("ik22EWsHI31EWDMBYxvITiILIrm2k9gEik22EWsHI3", None).len());
|
||||
assert_eq!(2, ser.decode("WsHI3WsHI31EWDMBYxvITiILIrm2k9gEik22Eik22E", None).len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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 data = ser.encode(&peers);
|
||||
let peers2 = ser.decode(&data, None);
|
||||
assert_eq!(format!("{:?}", peers), format!("{:?}", peers2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_file() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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 file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
|
||||
assert!(ser.write_to_file(&peers, file.path()).is_ok());
|
||||
let peers2 = ser.read_from_file(file.path(), None);
|
||||
assert!(peers2.is_ok());
|
||||
assert_eq!(format!("{:?}", peers), format!("{:?}", peers2.unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_cmd() {
|
||||
MockTimeSource::set_time(2000 * 3600);
|
||||
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 file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
|
||||
assert!(ser.write_to_cmd(&peers, &format!("echo $beacon > {}", file.path().display())).is_ok());
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
let res = ser.read_from_cmd(&format!("cat {}", file.path().display()), None);
|
||||
assert!(res.is_ok());
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
let peers2 = ser.get_cmd_results();
|
||||
assert!(peers2.is_some());
|
||||
assert_eq!(format!("{:?}", peers), format!("{:?}", peers2.unwrap()));
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
use test::Bencher;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
use super::udpmessage::{Options, Message, encode, decode};
|
||||
use super::crypto::{Crypto, CryptoMethod};
|
||||
use super::ethernet::{Frame, SwitchTable};
|
||||
use super::types::{Address, Table, Protocol};
|
||||
use super::ip::Packet;
|
||||
|
||||
#[bench]
|
||||
fn crypto_salsa20(b: &mut Bencher) {
|
||||
let mut crypto = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test");
|
||||
let mut payload = [0; 1500];
|
||||
let header = [0; 8];
|
||||
let mut nonce_bytes = [0; 12];
|
||||
b.iter(|| {
|
||||
let len = crypto.encrypt(&mut payload, 1400, &mut nonce_bytes, &header);
|
||||
assert!(crypto.decrypt(&mut payload[..len], &nonce_bytes, &header).is_ok())
|
||||
});
|
||||
b.bytes = 1400;
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn crypto_aes256(b: &mut Bencher) {
|
||||
if !Crypto::aes256_available() {
|
||||
return
|
||||
}
|
||||
let mut crypto = Crypto::from_shared_key(CryptoMethod::AES256, "test");
|
||||
let mut payload = [0; 1500];
|
||||
let header = [0; 8];
|
||||
let mut nonce_bytes = [0; 12];
|
||||
b.iter(|| {
|
||||
let len = crypto.encrypt(&mut payload, 1400, &mut nonce_bytes, &header);
|
||||
assert!(crypto.decrypt(&mut payload[..len], &nonce_bytes, &header).is_ok());
|
||||
});
|
||||
b.bytes = 1400;
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn message_encode(b: &mut Bencher) {
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::None;
|
||||
let mut payload = [0; 1600];
|
||||
let mut msg = Message::Data(&mut payload, 64, 1464);
|
||||
let mut buf = [0; 1600];
|
||||
b.iter(|| {
|
||||
encode(&mut options, &mut msg, &mut buf[..], &mut crypto);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn message_decode(b: &mut Bencher) {
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::None;
|
||||
let mut payload = [0; 1600];
|
||||
let mut msg = Message::Data(&mut payload, 64, 1464);
|
||||
let mut buf = [0; 1600];
|
||||
let mut res = encode(&mut options, &mut msg, &mut buf[..], &mut crypto);
|
||||
b.iter(|| {
|
||||
decode(&mut res, &mut crypto).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn switch_learn(b: &mut Bencher) {
|
||||
let mut table = SwitchTable::new(10);
|
||||
let addr = Address::from_str("12:34:56:78:90:ab").unwrap();
|
||||
let peer = "1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap();
|
||||
b.iter(|| {
|
||||
table.learn(addr.clone(), None, peer);
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn switch_lookup(b: &mut Bencher) {
|
||||
let mut table = SwitchTable::new(10);
|
||||
let addr = Address::from_str("12:34:56:78:90:ab").unwrap();
|
||||
let peer = "1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap();
|
||||
table.learn(addr.clone(), None, peer);
|
||||
b.iter(|| {
|
||||
table.lookup(&addr);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn ethernet_parse(b: &mut Bencher) {
|
||||
let mut data = [0; 1500];
|
||||
data[5] = 45;
|
||||
b.iter(|| {
|
||||
Frame::parse(&data).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn ipv4_parse(b: &mut Bencher) {
|
||||
let mut data = [0; 1500];
|
||||
data[0] = 4*16;
|
||||
b.iter(|| {
|
||||
Packet::parse(&data).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn ipv6_parse(b: &mut Bencher) {
|
||||
let mut data = [0; 1500];
|
||||
data[0] = 6*16;
|
||||
b.iter(|| {
|
||||
Packet::parse(&data).unwrap()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
default: libtuntap.a
|
||||
|
||||
tapdev.o: tuntap.c
|
||||
gcc -Os -c tuntap.c
|
||||
|
||||
libtapdev.a: tuntap.o
|
||||
ar rcs libtuntap.a tuntap.o
|
|
@ -0,0 +1,23 @@
|
|||
#include <stdint.h>
|
||||
#include <net/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
int32_t setup_device(int32_t fd, char *ifname, int32_t flags) {
|
||||
struct ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_flags = flags;
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
|
||||
if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) return 1;
|
||||
strncpy(ifname, ifr.ifr_name, IFNAMSIZ);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t setup_tap_device(int32_t fd, char *ifname) {
|
||||
return setup_device(fd, ifname, IFF_TAP | IFF_NO_PI);
|
||||
}
|
||||
|
||||
int32_t setup_tun_device(int32_t fd, char *ifname) {
|
||||
return setup_device(fd, ifname, IFF_TUN | IFF_NO_PI);
|
||||
}
|
1228
src/cloud.rs
1228
src/cloud.rs
File diff suppressed because it is too large
Load Diff
870
src/config.rs
870
src/config.rs
|
@ -1,870 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use super::{device::Type, types::Mode, util::run_cmd, util::Duration};
|
||||
pub use crate::crypto::Config as CryptoConfig;
|
||||
|
||||
use std::{cmp::max, collections::HashMap, ffi::OsStr, process, thread};
|
||||
use structopt::{clap::Shell, StructOpt};
|
||||
|
||||
pub const DEFAULT_PEER_TIMEOUT: u16 = 300;
|
||||
pub const DEFAULT_PORT: u16 = 3210;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Config {
|
||||
pub device_type: Type,
|
||||
pub device_name: String,
|
||||
pub device_path: Option<String>,
|
||||
pub fix_rp_filter: bool,
|
||||
|
||||
pub ip: Option<String>,
|
||||
pub advertise_addresses: Vec<String>,
|
||||
pub ifup: Option<String>,
|
||||
pub ifdown: Option<String>,
|
||||
|
||||
pub crypto: CryptoConfig,
|
||||
|
||||
pub listen: String,
|
||||
pub peers: Vec<String>,
|
||||
pub peer_timeout: Duration,
|
||||
pub keepalive: Option<Duration>,
|
||||
pub beacon_store: Option<String>,
|
||||
pub beacon_load: Option<String>,
|
||||
pub beacon_interval: Duration,
|
||||
pub beacon_password: Option<String>,
|
||||
pub mode: Mode,
|
||||
pub switch_timeout: Duration,
|
||||
pub claims: Vec<String>,
|
||||
pub auto_claim: bool,
|
||||
pub port_forwarding: bool,
|
||||
pub daemonize: bool,
|
||||
pub pid_file: Option<String>,
|
||||
pub stats_file: Option<String>,
|
||||
pub statsd_server: Option<String>,
|
||||
pub statsd_prefix: Option<String>,
|
||||
pub user: Option<String>,
|
||||
pub group: Option<String>,
|
||||
pub hook: Option<String>,
|
||||
pub hooks: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
device_type: Type::Tun,
|
||||
device_name: "vpncloud%d".to_string(),
|
||||
device_path: None,
|
||||
fix_rp_filter: false,
|
||||
ip: None,
|
||||
advertise_addresses: vec![],
|
||||
ifup: None,
|
||||
ifdown: None,
|
||||
crypto: CryptoConfig::default(),
|
||||
listen: "3210".to_string(),
|
||||
peers: vec![],
|
||||
peer_timeout: DEFAULT_PEER_TIMEOUT as Duration,
|
||||
keepalive: None,
|
||||
beacon_store: None,
|
||||
beacon_load: None,
|
||||
beacon_interval: 3600,
|
||||
beacon_password: None,
|
||||
mode: Mode::Normal,
|
||||
switch_timeout: 300,
|
||||
claims: vec![],
|
||||
auto_claim: true,
|
||||
port_forwarding: true,
|
||||
daemonize: false,
|
||||
pid_file: None,
|
||||
stats_file: None,
|
||||
statsd_server: None,
|
||||
statsd_prefix: None,
|
||||
user: None,
|
||||
group: None,
|
||||
hook: None,
|
||||
hooks: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn merge_file(&mut self, mut file: ConfigFile) {
|
||||
if let Some(device) = file.device {
|
||||
if let Some(val) = device.type_ {
|
||||
self.device_type = val;
|
||||
}
|
||||
if let Some(val) = device.name {
|
||||
self.device_name = val;
|
||||
}
|
||||
if let Some(val) = device.path {
|
||||
self.device_path = Some(val);
|
||||
}
|
||||
if let Some(val) = device.fix_rp_filter {
|
||||
self.fix_rp_filter = val;
|
||||
}
|
||||
}
|
||||
if let Some(val) = file.ip {
|
||||
self.ip = Some(val);
|
||||
}
|
||||
if let Some(mut val) = file.advertise_addresses {
|
||||
self.advertise_addresses.append(&mut val);
|
||||
}
|
||||
if let Some(val) = file.ifup {
|
||||
self.ifup = Some(val);
|
||||
}
|
||||
if let Some(val) = file.ifdown {
|
||||
self.ifdown = Some(val);
|
||||
}
|
||||
if let Some(val) = file.listen {
|
||||
self.listen = val;
|
||||
}
|
||||
if let Some(mut val) = file.peers {
|
||||
self.peers.append(&mut val);
|
||||
}
|
||||
if let Some(val) = file.peer_timeout {
|
||||
self.peer_timeout = val;
|
||||
}
|
||||
if let Some(val) = file.keepalive {
|
||||
self.keepalive = Some(val);
|
||||
}
|
||||
if let Some(beacon) = file.beacon {
|
||||
if let Some(val) = beacon.store {
|
||||
self.beacon_store = Some(val);
|
||||
}
|
||||
if let Some(val) = beacon.load {
|
||||
self.beacon_load = Some(val);
|
||||
}
|
||||
if let Some(val) = beacon.interval {
|
||||
self.beacon_interval = val;
|
||||
}
|
||||
if let Some(val) = beacon.password {
|
||||
self.beacon_password = Some(val);
|
||||
}
|
||||
}
|
||||
if let Some(val) = file.mode {
|
||||
self.mode = val;
|
||||
}
|
||||
if let Some(val) = file.switch_timeout {
|
||||
self.switch_timeout = val;
|
||||
}
|
||||
if let Some(mut val) = file.claims {
|
||||
self.claims.append(&mut val);
|
||||
}
|
||||
if let Some(val) = file.auto_claim {
|
||||
self.auto_claim = val;
|
||||
}
|
||||
if let Some(val) = file.port_forwarding {
|
||||
self.port_forwarding = val;
|
||||
}
|
||||
if let Some(val) = file.pid_file {
|
||||
self.pid_file = Some(val);
|
||||
}
|
||||
if let Some(val) = file.stats_file {
|
||||
self.stats_file = Some(val);
|
||||
}
|
||||
if let Some(statsd) = file.statsd {
|
||||
if let Some(val) = statsd.server {
|
||||
self.statsd_server = Some(val);
|
||||
}
|
||||
if let Some(val) = statsd.prefix {
|
||||
self.statsd_prefix = Some(val);
|
||||
}
|
||||
}
|
||||
if let Some(val) = file.user {
|
||||
self.user = Some(val);
|
||||
}
|
||||
if let Some(val) = file.group {
|
||||
self.group = Some(val);
|
||||
}
|
||||
if let Some(val) = file.crypto.password {
|
||||
self.crypto.password = Some(val)
|
||||
}
|
||||
if let Some(val) = file.crypto.public_key {
|
||||
self.crypto.public_key = Some(val)
|
||||
}
|
||||
if let Some(val) = file.crypto.private_key {
|
||||
self.crypto.private_key = Some(val)
|
||||
}
|
||||
self.crypto.trusted_keys.append(&mut file.crypto.trusted_keys);
|
||||
if !file.crypto.algorithms.is_empty() {
|
||||
self.crypto.algorithms = file.crypto.algorithms.clone();
|
||||
}
|
||||
if let Some(val) = file.hook {
|
||||
self.hook = Some(val)
|
||||
}
|
||||
for (k, v) in file.hooks {
|
||||
self.hooks.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_args(&mut self, mut args: Args) {
|
||||
if let Some(val) = args.type_ {
|
||||
self.device_type = val;
|
||||
}
|
||||
if let Some(val) = args.device {
|
||||
self.device_name = val;
|
||||
}
|
||||
if let Some(val) = args.device_path {
|
||||
self.device_path = Some(val);
|
||||
}
|
||||
if args.fix_rp_filter {
|
||||
self.fix_rp_filter = true;
|
||||
}
|
||||
if let Some(val) = args.ip {
|
||||
self.ip = Some(val);
|
||||
}
|
||||
if let Some(val) = args.ifup {
|
||||
self.ifup = Some(val);
|
||||
}
|
||||
self.advertise_addresses.append(&mut args.advertise_addresses);
|
||||
if let Some(val) = args.ifdown {
|
||||
self.ifdown = Some(val);
|
||||
}
|
||||
if let Some(val) = args.listen {
|
||||
self.listen = val;
|
||||
}
|
||||
self.peers.append(&mut args.peers);
|
||||
if let Some(val) = args.peer_timeout {
|
||||
self.peer_timeout = val;
|
||||
}
|
||||
if let Some(val) = args.keepalive {
|
||||
self.keepalive = Some(val);
|
||||
}
|
||||
if let Some(val) = args.beacon_store {
|
||||
self.beacon_store = Some(val);
|
||||
}
|
||||
if let Some(val) = args.beacon_load {
|
||||
self.beacon_load = Some(val);
|
||||
}
|
||||
if let Some(val) = args.beacon_interval {
|
||||
self.beacon_interval = val;
|
||||
}
|
||||
if let Some(val) = args.beacon_password {
|
||||
self.beacon_password = Some(val);
|
||||
}
|
||||
if let Some(val) = args.mode {
|
||||
self.mode = val;
|
||||
}
|
||||
if let Some(val) = args.switch_timeout {
|
||||
self.switch_timeout = val;
|
||||
}
|
||||
self.claims.append(&mut args.claims);
|
||||
if args.no_auto_claim {
|
||||
self.auto_claim = false;
|
||||
}
|
||||
if args.no_port_forwarding {
|
||||
self.port_forwarding = false;
|
||||
}
|
||||
if args.daemon {
|
||||
self.daemonize = true;
|
||||
}
|
||||
if let Some(val) = args.pid_file {
|
||||
self.pid_file = Some(val);
|
||||
}
|
||||
if let Some(val) = args.stats_file {
|
||||
self.stats_file = Some(val);
|
||||
}
|
||||
if let Some(val) = args.statsd_server {
|
||||
self.statsd_server = Some(val);
|
||||
}
|
||||
if let Some(val) = args.statsd_prefix {
|
||||
self.statsd_prefix = Some(val);
|
||||
}
|
||||
if let Some(val) = args.user {
|
||||
self.user = Some(val);
|
||||
}
|
||||
if let Some(val) = args.group {
|
||||
self.group = Some(val);
|
||||
}
|
||||
if let Some(val) = args.password {
|
||||
self.crypto.password = Some(val)
|
||||
}
|
||||
if let Some(val) = args.public_key {
|
||||
self.crypto.public_key = Some(val)
|
||||
}
|
||||
if let Some(val) = args.private_key {
|
||||
self.crypto.private_key = Some(val)
|
||||
}
|
||||
self.crypto.trusted_keys.append(&mut args.trusted_keys);
|
||||
if !args.algorithms.is_empty() {
|
||||
self.crypto.algorithms = args.algorithms.clone();
|
||||
}
|
||||
for s in args.hook {
|
||||
if s.contains(':') {
|
||||
let pos = s.find(':').unwrap();
|
||||
let name = &s[..pos];
|
||||
let hook = &s[pos + 1..];
|
||||
self.hooks.insert(name.to_string(), hook.to_string());
|
||||
} else {
|
||||
self.hook = Some(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
advertise_addresses: Some(self.advertise_addresses),
|
||||
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,
|
||||
None => max(self.peer_timeout / 2 - 60, 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_hook(
|
||||
&self, event: &'static str, envs: impl IntoIterator<Item = (&'static str, impl AsRef<OsStr>)>, detach: bool,
|
||||
) {
|
||||
let mut script = None;
|
||||
if let Some(ref s) = self.hook {
|
||||
script = Some(s);
|
||||
}
|
||||
if let Some(s) = self.hooks.get(event) {
|
||||
script = Some(s);
|
||||
}
|
||||
if script.is_none() {
|
||||
return;
|
||||
}
|
||||
let script = script.unwrap();
|
||||
let mut cmd = process::Command::new("sh");
|
||||
cmd.arg("-c").arg(script).envs(envs).env("EVENT", event);
|
||||
debug!("Running event script: {:?}", cmd);
|
||||
if detach {
|
||||
thread::spawn(move || run_cmd(cmd));
|
||||
} else {
|
||||
run_cmd(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug, Default)]
|
||||
pub struct Args {
|
||||
/// Read configuration options from the specified file.
|
||||
#[structopt(long)]
|
||||
pub config: Option<String>,
|
||||
|
||||
/// Set the type of network
|
||||
#[structopt(name = "type", short, long, possible_values=&["tun", "tap"])]
|
||||
pub type_: Option<Type>,
|
||||
|
||||
/// Set the path of the base device
|
||||
#[structopt(long)]
|
||||
pub device_path: Option<String>,
|
||||
|
||||
/// Fix the rp_filter settings on the host
|
||||
#[structopt(long)]
|
||||
pub fix_rp_filter: bool,
|
||||
|
||||
/// The mode of the VPN
|
||||
#[structopt(short, long, possible_values=&["normal", "router", "switch", "hub"])]
|
||||
pub mode: Option<Mode>,
|
||||
|
||||
/// The shared password to encrypt all traffic
|
||||
#[structopt(short, long, env)]
|
||||
pub password: Option<String>,
|
||||
|
||||
/// The private key to use
|
||||
#[structopt(long, alias = "key", conflicts_with = "password", env)]
|
||||
pub private_key: Option<String>,
|
||||
|
||||
/// The public key to use
|
||||
#[structopt(long)]
|
||||
pub public_key: Option<String>,
|
||||
|
||||
/// Other public keys to trust
|
||||
#[structopt(long = "trusted-key", alias = "trust", use_delimiter = true)]
|
||||
pub trusted_keys: Vec<String>,
|
||||
|
||||
/// Algorithms to allow
|
||||
#[structopt(long = "algorithm", alias = "algo", use_delimiter=true, case_insensitive = true, possible_values=&["plain", "aes128", "aes256", "chacha20"])]
|
||||
pub algorithms: Vec<String>,
|
||||
|
||||
/// The local subnets to claim (IP or IP/prefix)
|
||||
#[structopt(long = "claim", use_delimiter = true)]
|
||||
pub claims: Vec<String>,
|
||||
|
||||
/// Do not automatically claim the device ip
|
||||
#[structopt(long)]
|
||||
pub no_auto_claim: bool,
|
||||
|
||||
/// Name of the virtual device
|
||||
#[structopt(short, long)]
|
||||
pub device: Option<String>,
|
||||
|
||||
/// The port number (or ip:port) on which to listen for data
|
||||
#[structopt(short, long)]
|
||||
pub listen: Option<String>,
|
||||
|
||||
/// Address of a peer to connect to
|
||||
#[structopt(short = "c", long = "peer", alias = "connect")]
|
||||
pub peers: Vec<String>,
|
||||
|
||||
/// Peer timeout in seconds
|
||||
#[structopt(long)]
|
||||
pub peer_timeout: Option<Duration>,
|
||||
|
||||
/// Periodically send message to keep connections alive
|
||||
#[structopt(long)]
|
||||
pub keepalive: Option<Duration>,
|
||||
|
||||
/// Switch table entry timeout in seconds
|
||||
#[structopt(long)]
|
||||
pub switch_timeout: Option<Duration>,
|
||||
|
||||
/// The file path or |command to store the beacon
|
||||
#[structopt(long)]
|
||||
pub beacon_store: Option<String>,
|
||||
|
||||
/// The file path or |command to load the beacon
|
||||
#[structopt(long)]
|
||||
pub beacon_load: Option<String>,
|
||||
|
||||
/// Beacon store/load interval in seconds
|
||||
#[structopt(long)]
|
||||
pub beacon_interval: Option<Duration>,
|
||||
|
||||
/// Password to encrypt the beacon with
|
||||
#[structopt(long)]
|
||||
pub beacon_password: Option<String>,
|
||||
|
||||
/// Print debug information
|
||||
#[structopt(short, long, conflicts_with = "quiet")]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Only print errors and warnings
|
||||
#[structopt(short, long)]
|
||||
pub quiet: bool,
|
||||
|
||||
/// An IP address (plus optional prefix length) for the interface
|
||||
#[structopt(long)]
|
||||
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
|
||||
#[structopt(long)]
|
||||
pub ifup: Option<String>,
|
||||
|
||||
/// A command to bring down the network interface
|
||||
#[structopt(long)]
|
||||
pub ifdown: Option<String>,
|
||||
|
||||
/// Print the version and exit
|
||||
#[structopt(long)]
|
||||
pub version: bool,
|
||||
|
||||
/// Disable automatic port forwarding
|
||||
#[structopt(long)]
|
||||
pub no_port_forwarding: bool,
|
||||
|
||||
/// Run the process in the background
|
||||
#[structopt(long)]
|
||||
pub daemon: bool,
|
||||
|
||||
/// Store the process id in this file when daemonizing
|
||||
#[structopt(long)]
|
||||
pub pid_file: Option<String>,
|
||||
|
||||
/// Print statistics to this file
|
||||
#[structopt(long)]
|
||||
pub stats_file: Option<String>,
|
||||
|
||||
/// Send statistics to this statsd server
|
||||
#[structopt(long)]
|
||||
pub statsd_server: Option<String>,
|
||||
|
||||
/// Use the given prefix for statsd records
|
||||
#[structopt(long, requires = "statsd-server")]
|
||||
pub statsd_prefix: Option<String>,
|
||||
|
||||
/// Run as other user
|
||||
#[structopt(long)]
|
||||
pub user: Option<String>,
|
||||
|
||||
/// Run as other group
|
||||
#[structopt(long)]
|
||||
pub group: Option<String>,
|
||||
|
||||
/// Print logs also to this file
|
||||
#[structopt(long)]
|
||||
pub log_file: Option<String>,
|
||||
|
||||
/// Call script on event
|
||||
#[structopt(long)]
|
||||
pub hook: Vec<String>,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub cmd: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum Command {
|
||||
/// Generate and print a key-pair and exit
|
||||
#[structopt(name = "genkey", alias = "gen-key")]
|
||||
GenKey {
|
||||
/// The shared password to encrypt all traffic
|
||||
#[structopt(short, long, env)]
|
||||
password: Option<String>,
|
||||
},
|
||||
|
||||
/// Run a websocket proxy
|
||||
#[cfg(feature = "websocket")]
|
||||
#[structopt(alias = "wsproxy")]
|
||||
WsProxy {
|
||||
/// Websocket listen address IP:PORT
|
||||
#[structopt(long, short, default_value = "3210")]
|
||||
listen: String,
|
||||
},
|
||||
|
||||
/// Migrate an old config file
|
||||
#[structopt(alias = "migrate")]
|
||||
MigrateConfig {
|
||||
/// Config file
|
||||
#[structopt(long)]
|
||||
config_file: String,
|
||||
},
|
||||
|
||||
/// Generate shell completions
|
||||
Completion {
|
||||
/// Shell to create completions for
|
||||
#[structopt(long, 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,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||
pub struct ConfigFileDevice {
|
||||
#[serde(rename = "type")]
|
||||
pub type_: Option<Type>,
|
||||
pub name: Option<String>,
|
||||
pub path: Option<String>,
|
||||
pub fix_rp_filter: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||
pub struct ConfigFileBeacon {
|
||||
pub store: Option<String>,
|
||||
pub load: Option<String>,
|
||||
pub interval: Option<Duration>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||
pub struct ConfigFileStatsd {
|
||||
pub server: Option<String>,
|
||||
pub prefix: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||
pub struct ConfigFile {
|
||||
pub device: Option<ConfigFileDevice>,
|
||||
|
||||
pub ip: Option<String>,
|
||||
pub advertise_addresses: Option<Vec<String>>,
|
||||
pub ifup: Option<String>,
|
||||
pub ifdown: Option<String>,
|
||||
|
||||
pub crypto: CryptoConfig,
|
||||
pub listen: Option<String>,
|
||||
pub peers: Option<Vec<String>>,
|
||||
pub peer_timeout: Option<Duration>,
|
||||
pub keepalive: Option<Duration>,
|
||||
|
||||
pub beacon: Option<ConfigFileBeacon>,
|
||||
pub mode: Option<Mode>,
|
||||
pub switch_timeout: Option<Duration>,
|
||||
pub claims: Option<Vec<String>>,
|
||||
pub auto_claim: Option<bool>,
|
||||
pub port_forwarding: Option<bool>,
|
||||
pub pid_file: Option<String>,
|
||||
pub stats_file: Option<String>,
|
||||
pub statsd: Option<ConfigFileStatsd>,
|
||||
pub user: Option<String>,
|
||||
pub group: Option<String>,
|
||||
pub hook: Option<String>,
|
||||
pub hooks: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file() {
|
||||
let config_file = "
|
||||
device:
|
||||
type: tun
|
||||
name: vpncloud%d
|
||||
path: /dev/net/tun
|
||||
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
|
||||
ifdown: 'true'
|
||||
peers:
|
||||
- remote.machine.foo:3210
|
||||
- remote.machine.bar:3210
|
||||
peer-timeout: 600
|
||||
keepalive: 840
|
||||
switch-timeout: 300
|
||||
beacon:
|
||||
store: /run/vpncloud.beacon.out
|
||||
load: /run/vpncloud.beacon.in
|
||||
interval: 3600
|
||||
password: test123
|
||||
mode: normal
|
||||
claims:
|
||||
- 10.0.1.0/24
|
||||
port-forwarding: true
|
||||
user: nobody
|
||||
group: nogroup
|
||||
pid-file: /run/vpncloud.run
|
||||
stats-file: /var/log/vpncloud.stats
|
||||
statsd:
|
||||
server: example.com:1234
|
||||
prefix: prefix
|
||||
";
|
||||
assert_eq!(
|
||||
serde_yaml::from_str::<ConfigFile>(config_file).unwrap(),
|
||||
ConfigFile {
|
||||
device: Some(ConfigFileDevice {
|
||||
type_: Some(Type::Tun),
|
||||
name: Some("vpncloud%d".to_string()),
|
||||
path: Some("/dev/net/tun".to_string()),
|
||||
fix_rp_filter: None
|
||||
}),
|
||||
ip: Some("10.0.1.1/16".to_string()),
|
||||
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()),
|
||||
ifdown: Some("true".to_string()),
|
||||
crypto: CryptoConfig::default(),
|
||||
listen: None,
|
||||
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
|
||||
peer_timeout: Some(600),
|
||||
keepalive: Some(840),
|
||||
beacon: Some(ConfigFileBeacon {
|
||||
store: Some("/run/vpncloud.beacon.out".to_string()),
|
||||
load: Some("/run/vpncloud.beacon.in".to_string()),
|
||||
interval: Some(3600),
|
||||
password: Some("test123".to_string())
|
||||
}),
|
||||
mode: Some(Mode::Normal),
|
||||
switch_timeout: Some(300),
|
||||
claims: Some(vec!["10.0.1.0/24".to_string()]),
|
||||
auto_claim: None,
|
||||
port_forwarding: Some(true),
|
||||
user: Some("nobody".to_string()),
|
||||
group: Some("nogroup".to_string()),
|
||||
pid_file: Some("/run/vpncloud.run".to_string()),
|
||||
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
||||
statsd: Some(ConfigFileStatsd {
|
||||
server: Some("example.com:1234".to_string()),
|
||||
prefix: Some("prefix".to_string())
|
||||
}),
|
||||
hook: None,
|
||||
hooks: HashMap::new()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_example_config() {
|
||||
serde_yaml::from_str::<ConfigFile>(include_str!("../assets/example.net.disabled")).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_merge() {
|
||||
let mut config = Config::default();
|
||||
config.merge_file(ConfigFile {
|
||||
device: Some(ConfigFileDevice {
|
||||
type_: Some(Type::Tun),
|
||||
name: Some("vpncloud%d".to_string()),
|
||||
path: None,
|
||||
fix_rp_filter: None,
|
||||
}),
|
||||
ip: None,
|
||||
advertise_addresses: Some(vec![]),
|
||||
ifup: Some("ifconfig $IFNAME 10.0.1.1/16 mtu 1400 up".to_string()),
|
||||
ifdown: Some("true".to_string()),
|
||||
crypto: CryptoConfig::default(),
|
||||
listen: None,
|
||||
peers: Some(vec!["remote.machine.foo:3210".to_string(), "remote.machine.bar:3210".to_string()]),
|
||||
peer_timeout: Some(600),
|
||||
keepalive: Some(840),
|
||||
beacon: Some(ConfigFileBeacon {
|
||||
store: Some("/run/vpncloud.beacon.out".to_string()),
|
||||
load: Some("/run/vpncloud.beacon.in".to_string()),
|
||||
interval: Some(7200),
|
||||
password: Some("test123".to_string()),
|
||||
}),
|
||||
mode: Some(Mode::Normal),
|
||||
switch_timeout: Some(300),
|
||||
claims: Some(vec!["10.0.1.0/24".to_string()]),
|
||||
auto_claim: Some(true),
|
||||
port_forwarding: Some(true),
|
||||
user: Some("nobody".to_string()),
|
||||
group: Some("nogroup".to_string()),
|
||||
pid_file: Some("/run/vpncloud.run".to_string()),
|
||||
stats_file: Some("/var/log/vpncloud.stats".to_string()),
|
||||
statsd: Some(ConfigFileStatsd {
|
||||
server: Some("example.com:1234".to_string()),
|
||||
prefix: Some("prefix".to_string()),
|
||||
}),
|
||||
hook: None,
|
||||
hooks: HashMap::new(),
|
||||
});
|
||||
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 {
|
||||
type_: Some(Type::Tap),
|
||||
device: Some("vpncloud0".to_string()),
|
||||
device_path: Some("/dev/null".to_string()),
|
||||
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
|
||||
ifdown: Some("ifconfig $IFNAME down".to_string()),
|
||||
password: Some("anothersecret".to_string()),
|
||||
listen: Some("[::]:3211".to_string()),
|
||||
peer_timeout: Some(1801),
|
||||
keepalive: Some(850),
|
||||
switch_timeout: Some(301),
|
||||
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
|
||||
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
|
||||
beacon_interval: Some(3600),
|
||||
beacon_password: Some("test1234".to_string()),
|
||||
mode: Some(Mode::Switch),
|
||||
claims: vec![],
|
||||
peers: vec!["another:3210".to_string()],
|
||||
no_port_forwarding: true,
|
||||
daemon: true,
|
||||
pid_file: Some("/run/vpncloud-mynet.run".to_string()),
|
||||
stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()),
|
||||
statsd_server: Some("example.com:2345".to_string()),
|
||||
statsd_prefix: Some("prefix2".to_string()),
|
||||
user: Some("root".to_string()),
|
||||
group: Some("root".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(
|
||||
config,
|
||||
Config {
|
||||
device_type: Type::Tap,
|
||||
device_name: "vpncloud0".to_string(),
|
||||
device_path: Some("/dev/null".to_string()),
|
||||
fix_rp_filter: false,
|
||||
ip: None,
|
||||
advertise_addresses: vec![],
|
||||
|
||||
ifup: Some("ifconfig $IFNAME 10.0.1.2/16 mtu 1400 up".to_string()),
|
||||
ifdown: Some("ifconfig $IFNAME down".to_string()),
|
||||
crypto: CryptoConfig { password: Some("anothersecret".to_string()), ..CryptoConfig::default() },
|
||||
listen: "[::]:3211".to_string(),
|
||||
peers: vec![
|
||||
"remote.machine.foo:3210".to_string(),
|
||||
"remote.machine.bar:3210".to_string(),
|
||||
"another:3210".to_string()
|
||||
],
|
||||
peer_timeout: 1801,
|
||||
keepalive: Some(850),
|
||||
switch_timeout: 301,
|
||||
beacon_store: Some("/run/vpncloud.beacon.out2".to_string()),
|
||||
beacon_load: Some("/run/vpncloud.beacon.in2".to_string()),
|
||||
beacon_interval: 3600,
|
||||
beacon_password: Some("test1234".to_string()),
|
||||
mode: Mode::Switch,
|
||||
port_forwarding: false,
|
||||
claims: vec!["10.0.1.0/24".to_string()],
|
||||
auto_claim: true,
|
||||
user: Some("root".to_string()),
|
||||
group: Some("root".to_string()),
|
||||
pid_file: Some("/run/vpncloud-mynet.run".to_string()),
|
||||
stats_file: Some("/var/log/vpncloud-mynet.stats".to_string()),
|
||||
statsd_server: Some("example.com:2345".to_string()),
|
||||
statsd_prefix: Some("prefix2".to_string()),
|
||||
daemonize: true,
|
||||
hook: None,
|
||||
hooks: HashMap::new()
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
use std::ptr;
|
||||
use std::ffi::CStr;
|
||||
|
||||
use libc::{size_t, c_char, c_ulonglong, c_int};
|
||||
use aligned_alloc::{aligned_alloc, aligned_free};
|
||||
|
||||
use super::types::Error;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_chacha20poly1305_ietf_KEYBYTES: usize = 32;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_chacha20poly1305_ietf_NSECBYTES: usize = 0;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_chacha20poly1305_ietf_NPUBBYTES: usize = 12;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_chacha20poly1305_ietf_ABYTES: usize = 16;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_aes256gcm_KEYBYTES: usize = 32;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_aes256gcm_NSECBYTES: usize = 0;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_aes256gcm_NPUBBYTES: usize = 12;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_aes256gcm_ABYTES: usize = 16;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_aead_aes256gcm_STATEBYTES: usize = 512;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_pwhash_scryptsalsa208sha256_SALTBYTES: usize = 32;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_pwhash_scryptsalsa208sha256_STRBYTES: usize = 102;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE: usize = 524288;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE: usize = 16777216;
|
||||
|
||||
struct Aes256State(*mut [u8; crypto_aead_aes256gcm_STATEBYTES]);
|
||||
|
||||
impl Aes256State {
|
||||
fn new() -> Aes256State {
|
||||
let ptr = aligned_alloc(crypto_aead_aes256gcm_STATEBYTES, 16)
|
||||
as *mut [u8; crypto_aead_aes256gcm_STATEBYTES];
|
||||
Aes256State(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Aes256State {
|
||||
fn drop(&mut self) {
|
||||
unsafe { aligned_free(self.0 as *mut ()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[link(name="sodium", kind="static")]
|
||||
extern {
|
||||
pub fn sodium_init() -> c_int;
|
||||
pub fn randombytes_buf(buf: *mut u8, size: size_t);
|
||||
pub fn sodium_version_string() -> *const c_char;
|
||||
pub fn crypto_aead_aes256gcm_is_available() -> c_int;
|
||||
pub fn crypto_pwhash_scryptsalsa208sha256(
|
||||
out: *mut u8,
|
||||
outlen: c_ulonglong,
|
||||
passwd: *const u8,
|
||||
passwdlen: c_ulonglong,
|
||||
salt: *const [u8; crypto_pwhash_scryptsalsa208sha256_SALTBYTES],
|
||||
opslimit: c_ulonglong,
|
||||
memlimit: size_t) -> c_int;
|
||||
pub fn crypto_aead_chacha20poly1305_ietf_encrypt(
|
||||
c: *mut u8,
|
||||
clen: *mut c_ulonglong,
|
||||
m: *const u8,
|
||||
mlen: c_ulonglong,
|
||||
ad: *const u8,
|
||||
adlen: c_ulonglong,
|
||||
nsec: *const [u8; crypto_aead_chacha20poly1305_ietf_NSECBYTES],
|
||||
npub: *const [u8; crypto_aead_chacha20poly1305_ietf_NPUBBYTES],
|
||||
k: *const [u8; crypto_aead_chacha20poly1305_ietf_KEYBYTES]) -> c_int;
|
||||
pub fn crypto_aead_chacha20poly1305_ietf_decrypt(
|
||||
m: *mut u8,
|
||||
mlen: *mut c_ulonglong,
|
||||
nsec: *mut [u8; crypto_aead_chacha20poly1305_ietf_NSECBYTES],
|
||||
c: *const u8,
|
||||
clen: c_ulonglong,
|
||||
ad: *const u8,
|
||||
adlen: c_ulonglong,
|
||||
npub: *const [u8; crypto_aead_chacha20poly1305_ietf_NPUBBYTES],
|
||||
k: *const [u8; crypto_aead_chacha20poly1305_ietf_KEYBYTES]) -> c_int;
|
||||
pub fn crypto_aead_aes256gcm_beforenm(
|
||||
state: *mut [u8; crypto_aead_aes256gcm_STATEBYTES],
|
||||
k: *const [u8; crypto_aead_aes256gcm_KEYBYTES]) -> c_int;
|
||||
pub fn crypto_aead_aes256gcm_encrypt_afternm(
|
||||
c: *mut u8,
|
||||
clen: *mut c_ulonglong,
|
||||
m: *const u8,
|
||||
mlen: c_ulonglong,
|
||||
ad: *const u8,
|
||||
adlen: c_ulonglong,
|
||||
nsec: *const [u8; crypto_aead_aes256gcm_NSECBYTES],
|
||||
npub: *const [u8; crypto_aead_aes256gcm_NPUBBYTES],
|
||||
state: *const [u8; crypto_aead_aes256gcm_STATEBYTES]) -> c_int;
|
||||
pub fn crypto_aead_aes256gcm_decrypt_afternm(
|
||||
m: *mut u8,
|
||||
mlen: *mut c_ulonglong,
|
||||
nsec: *mut [u8; crypto_aead_aes256gcm_NSECBYTES],
|
||||
c: *const u8,
|
||||
clen: c_ulonglong,
|
||||
ad: *const u8,
|
||||
adlen: c_ulonglong,
|
||||
npub: *const [u8; crypto_aead_aes256gcm_NPUBBYTES],
|
||||
state: *const [u8; crypto_aead_aes256gcm_STATEBYTES]) -> c_int;
|
||||
}
|
||||
|
||||
|
||||
#[derive(RustcDecodable, Debug)]
|
||||
pub enum CryptoMethod {
|
||||
ChaCha20, AES256
|
||||
}
|
||||
|
||||
pub enum Crypto {
|
||||
None,
|
||||
ChaCha20Poly1305{
|
||||
key: [u8; crypto_aead_chacha20poly1305_ietf_KEYBYTES],
|
||||
nonce: [u8; crypto_aead_chacha20poly1305_ietf_NPUBBYTES]
|
||||
},
|
||||
AES256GCM{
|
||||
state: Aes256State,
|
||||
nonce: [u8; crypto_aead_aes256gcm_NPUBBYTES]
|
||||
}
|
||||
}
|
||||
|
||||
fn inc_nonce_12(nonce: &mut [u8; 12]) {
|
||||
for i in 0..12 {
|
||||
let mut num = nonce[11-i];
|
||||
num = num.wrapping_add(1);
|
||||
nonce[11-i] = num;
|
||||
if num > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Crypto {
|
||||
pub fn init() {
|
||||
if unsafe { sodium_init() } != 0 {
|
||||
fail!("Failed to initialize crypto library");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sodium_version() -> String {
|
||||
unsafe {
|
||||
CStr::from_ptr(sodium_version_string()).to_string_lossy().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aes256_available() -> bool {
|
||||
unsafe {
|
||||
crypto_aead_aes256gcm_is_available() == 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method(&self) -> u8 {
|
||||
match self {
|
||||
&Crypto::None => 0,
|
||||
&Crypto::ChaCha20Poly1305{key: _, nonce: _} => 1,
|
||||
&Crypto::AES256GCM{state: _, nonce: _} => 2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nonce_bytes(&self) -> usize {
|
||||
match self {
|
||||
&Crypto::None => 0,
|
||||
&Crypto::ChaCha20Poly1305{key: _, ref nonce} => nonce.len(),
|
||||
&Crypto::AES256GCM{state: _, ref nonce} => nonce.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn additional_bytes(&self) -> usize {
|
||||
match self {
|
||||
&Crypto::None => 0,
|
||||
&Crypto::ChaCha20Poly1305{key: _, nonce: _} => crypto_aead_chacha20poly1305_ietf_ABYTES,
|
||||
&Crypto::AES256GCM{state: _, nonce: _} => crypto_aead_aes256gcm_ABYTES
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_shared_key(method: CryptoMethod, password: &str) -> Self {
|
||||
let salt = "vpncloudVPNCLOUDvpncl0udVpnCloud".as_bytes();
|
||||
assert_eq!(salt.len(), crypto_pwhash_scryptsalsa208sha256_SALTBYTES);
|
||||
let mut key = [0; crypto_pwhash_scryptsalsa208sha256_STRBYTES];
|
||||
let res = unsafe { crypto_pwhash_scryptsalsa208sha256(
|
||||
key.as_mut_ptr(),
|
||||
key.len() as u64,
|
||||
password.as_bytes().as_ptr(),
|
||||
password.as_bytes().len() as u64,
|
||||
salt.as_ptr() as *const [u8; crypto_pwhash_scryptsalsa208sha256_SALTBYTES],
|
||||
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE as u64,
|
||||
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
|
||||
) };
|
||||
if res != 0 {
|
||||
fail!("Key derivation failed");
|
||||
}
|
||||
match method {
|
||||
CryptoMethod::ChaCha20 => {
|
||||
let mut crypto_key = [0; crypto_aead_chacha20poly1305_ietf_KEYBYTES];
|
||||
for i in 0..crypto_key.len() {
|
||||
crypto_key[i] = key[i];
|
||||
}
|
||||
let mut nonce = [0u8; crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
|
||||
unsafe { randombytes_buf(nonce.as_mut_ptr(), nonce.len()) };
|
||||
Crypto::ChaCha20Poly1305{key: crypto_key, nonce: nonce}
|
||||
},
|
||||
CryptoMethod::AES256 => {
|
||||
if ! Crypto::aes256_available() {
|
||||
fail!("AES256 is not supported by this processor, use ChaCha20 instead");
|
||||
}
|
||||
let mut nonce = [0u8; crypto_aead_aes256gcm_NPUBBYTES];
|
||||
unsafe { randombytes_buf(nonce.as_mut_ptr(), nonce.len()) };
|
||||
let state = Aes256State::new();
|
||||
let res = unsafe { crypto_aead_aes256gcm_beforenm(
|
||||
state.0,
|
||||
key[..crypto_aead_aes256gcm_KEYBYTES].as_ptr() as *const [u8; crypto_aead_aes256gcm_KEYBYTES]
|
||||
) };
|
||||
assert_eq!(res, 0);
|
||||
Crypto::AES256GCM{state: state, nonce: nonce}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, mut buf: &mut [u8], nonce: &[u8], header: &[u8]) -> Result<usize, Error> {
|
||||
match self {
|
||||
&Crypto::None => Ok(buf.len()),
|
||||
&Crypto::ChaCha20Poly1305{ref key, nonce: _} => {
|
||||
let mut mlen: u64 = buf.len() as u64;
|
||||
let res = unsafe { crypto_aead_chacha20poly1305_ietf_decrypt(
|
||||
buf.as_mut_ptr(), // Base pointer to buffer
|
||||
&mut mlen, // Mutable size of buffer (will be set to used size)
|
||||
ptr::null_mut::<[u8; 0]>(), // Mutable base pointer to secret nonce (always NULL)
|
||||
buf.as_ptr(), // Base pointer to message
|
||||
buf.len() as u64, // Size of message
|
||||
header.as_ptr(), // Base pointer to additional data
|
||||
header.len() as u64, // Size of additional data
|
||||
nonce.as_ptr() as *const [u8; crypto_aead_chacha20poly1305_ietf_NPUBBYTES], // Base pointer to public nonce
|
||||
key.as_ptr() as *const [u8; crypto_aead_chacha20poly1305_ietf_KEYBYTES] // Base pointer to key
|
||||
) };
|
||||
match res {
|
||||
0 => Ok(mlen as usize),
|
||||
_ => Err(Error::CryptoError("Failed to decrypt"))
|
||||
}
|
||||
},
|
||||
&Crypto::AES256GCM{ref state, nonce: _} => {
|
||||
let mut mlen: u64 = buf.len() as u64;
|
||||
let res = unsafe { crypto_aead_aes256gcm_decrypt_afternm(
|
||||
buf.as_mut_ptr(), // Base pointer to buffer
|
||||
&mut mlen, // Mutable size of buffer (will be set to used size)
|
||||
ptr::null_mut::<[u8; 0]>(), // Mutable base pointer to secret nonce (always NULL)
|
||||
buf.as_ptr(), // Base pointer to message
|
||||
buf.len() as u64, // Size of message
|
||||
header.as_ptr(), // Base pointer to additional data
|
||||
header.len() as u64, // Size of additional data
|
||||
nonce.as_ptr() as *const [u8; crypto_aead_aes256gcm_NPUBBYTES], // Base pointer to public nonce
|
||||
state.0 // Base pointer to state
|
||||
) };
|
||||
match res {
|
||||
0 => Ok(mlen as usize),
|
||||
_ => Err(Error::CryptoError("Failed to decrypt"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, mut buf: &mut [u8], mlen: usize, nonce_bytes: &mut [u8], header: &[u8]) -> usize {
|
||||
match self {
|
||||
&mut Crypto::None => mlen,
|
||||
&mut Crypto::ChaCha20Poly1305{ref key, ref mut nonce} => {
|
||||
inc_nonce_12(nonce);
|
||||
let mut clen: u64 = buf.len() as u64;
|
||||
assert!(nonce_bytes.len() == nonce.len());
|
||||
assert!(clen as usize >= mlen + crypto_aead_chacha20poly1305_ietf_ABYTES);
|
||||
let res = unsafe { crypto_aead_chacha20poly1305_ietf_encrypt(
|
||||
buf.as_mut_ptr(), // Base pointer to buffer
|
||||
&mut clen, // Mutable size of buffer (will be set to used size)
|
||||
buf.as_ptr(), // Base pointer to message
|
||||
mlen as u64, // Size of message
|
||||
header.as_ptr(), // Base pointer to additional data
|
||||
header.len() as u64, // Size of additional data
|
||||
ptr::null::<[u8; 0]>(), // Base pointer to secret nonce (always NULL)
|
||||
nonce.as_ptr() as *const [u8; crypto_aead_chacha20poly1305_ietf_NPUBBYTES], // Base pointer to public nonce
|
||||
key.as_ptr() as *const [u8; crypto_aead_chacha20poly1305_ietf_KEYBYTES] // Base pointer to key
|
||||
) };
|
||||
assert_eq!(res, 0);
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(nonce.as_ptr(), nonce_bytes.as_mut_ptr(), nonce.len());
|
||||
}
|
||||
clen as usize
|
||||
},
|
||||
&mut Crypto::AES256GCM{ref state, ref mut nonce} => {
|
||||
inc_nonce_12(nonce);
|
||||
let mut clen: u64 = buf.len() as u64;
|
||||
assert!(nonce_bytes.len() == nonce.len());
|
||||
assert!(clen as usize >= mlen + crypto_aead_aes256gcm_ABYTES);
|
||||
let res = unsafe { crypto_aead_aes256gcm_encrypt_afternm(
|
||||
buf.as_mut_ptr(), // Base pointer to buffer
|
||||
&mut clen, // Mutable size of buffer (will be set to used size)
|
||||
buf.as_ptr(), // Base pointer to message
|
||||
mlen as u64, // Size of message
|
||||
header.as_ptr(), // Base pointer to additional data
|
||||
header.len() as u64, // Size of additional data
|
||||
ptr::null::<[u8; 0]>(), // Base pointer to secret nonce (always NULL)
|
||||
nonce.as_ptr() as *const [u8; crypto_aead_aes256gcm_NPUBBYTES], // Base pointer to public nonce
|
||||
state.0 // Base pointer to state
|
||||
) };
|
||||
assert_eq!(res, 0);
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(nonce.as_ptr(), nonce_bytes.as_mut_ptr(), nonce.len());
|
||||
}
|
||||
clen as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,507 +0,0 @@
|
|||
use super::{
|
||||
core::{test_speed, CryptoCore},
|
||||
init::{self, InitResult, InitState, CLOSING},
|
||||
rotate::RotationState,
|
||||
};
|
||||
use crate::{
|
||||
error::Error,
|
||||
types::NodeId,
|
||||
util::{from_base62, to_base62, MsgBuffer},
|
||||
};
|
||||
use ring::{
|
||||
aead::{self, Algorithm, LessSafeKey, UnboundKey},
|
||||
agreement::{EphemeralPrivateKey, UnparsedPublicKey},
|
||||
pbkdf2,
|
||||
rand::{SecureRandom, SystemRandom},
|
||||
signature::{Ed25519KeyPair, KeyPair, ED25519_PUBLIC_KEY_LEN},
|
||||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{fmt::Debug, io::Read, num::NonZeroU32, sync::Arc, time::Duration};
|
||||
|
||||
const SALT: &[u8; 32] = b"vpncloudVPNCLOUDvpncl0udVpnCloud";
|
||||
const INIT_MESSAGE_FIRST_BYTE: u8 = 0xff;
|
||||
const MESSAGE_TYPE_ROTATION: u8 = 0x10;
|
||||
|
||||
pub type Ed25519PublicKey = [u8; ED25519_PUBLIC_KEY_LEN];
|
||||
pub type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>;
|
||||
pub type EcdhPrivateKey = EphemeralPrivateKey;
|
||||
pub type Key = SmallVec<[u8; 32]>;
|
||||
|
||||
const DEFAULT_ALGORITHMS: [&str; 3] = ["AES128", "AES256", "CHACHA20"];
|
||||
|
||||
#[cfg(test)]
|
||||
const SPEED_TEST_TIME: f32 = 0.02;
|
||||
#[cfg(not(test))]
|
||||
const SPEED_TEST_TIME: f32 = 0.1;
|
||||
|
||||
const ROTATE_INTERVAL: usize = 120;
|
||||
|
||||
pub trait Payload: Debug + PartialEq + Sized {
|
||||
fn write_to(&self, buffer: &mut MsgBuffer);
|
||||
fn read_from<R: Read>(r: R) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Algorithms {
|
||||
pub algorithm_speeds: SmallVec<[(&'static Algorithm, f32); 3]>,
|
||||
pub allow_unencrypted: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||
pub struct Config {
|
||||
pub password: Option<String>,
|
||||
pub private_key: Option<String>,
|
||||
pub public_key: Option<String>,
|
||||
pub trusted_keys: Vec<String>,
|
||||
pub algorithms: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct Crypto {
|
||||
node_id: NodeId,
|
||||
key_pair: Arc<Ed25519KeyPair>,
|
||||
trusted_keys: Arc<[Ed25519PublicKey]>,
|
||||
algorithms: Algorithms,
|
||||
}
|
||||
|
||||
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 {
|
||||
Self::parse_keypair(priv_key, pub_key)?
|
||||
} else {
|
||||
Self::parse_private_key(priv_key)?
|
||||
}
|
||||
} else if let Some(password) = &config.password {
|
||||
Self::keypair_from_password(password)
|
||||
} else {
|
||||
return Err(Error::InvalidConfig("Either private_key or password must be set"));
|
||||
};
|
||||
let mut trusted_keys = vec![];
|
||||
for tn in &config.trusted_keys {
|
||||
trusted_keys.push(Self::parse_public_key(tn)?);
|
||||
}
|
||||
if trusted_keys.is_empty() {
|
||||
info!("Trusted keys not set, trusting only own public key");
|
||||
let mut key = [0; ED25519_PUBLIC_KEY_LEN];
|
||||
key.clone_from_slice(key_pair.public_key().as_ref());
|
||||
trusted_keys.push(key);
|
||||
}
|
||||
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 algo in allowed_algos {
|
||||
let speed = test_speed(algo, &duration);
|
||||
algos.algorithm_speeds.push((algo, speed as f32));
|
||||
speeds.push((format!("{:?}", algo), speed as f32));
|
||||
}
|
||||
if !speeds.is_empty() {
|
||||
info!(
|
||||
"Crypto speeds: {}",
|
||||
speeds.into_iter().map(|(a, s)| format!("{}: {:.1} MiB/s", a, s)).collect::<Vec<_>>().join(", ")
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
node_id,
|
||||
key_pair: Arc::new(key_pair),
|
||||
trusted_keys: trusted_keys.into_boxed_slice().into(),
|
||||
algorithms: algos,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_keypair(password: Option<&str>) -> (String, String) {
|
||||
let mut bytes = [0; 32];
|
||||
match password {
|
||||
None => {
|
||||
let rng = SystemRandom::new();
|
||||
rng.fill(&mut bytes).unwrap();
|
||||
}
|
||||
Some(password) => {
|
||||
pbkdf2::derive(
|
||||
pbkdf2::PBKDF2_HMAC_SHA256,
|
||||
NonZeroU32::new(4096).unwrap(),
|
||||
SALT,
|
||||
password.as_bytes(),
|
||||
&mut bytes,
|
||||
);
|
||||
}
|
||||
}
|
||||
let keypair = Ed25519KeyPair::from_seed_unchecked(&bytes).unwrap();
|
||||
let privkey = to_base62(&bytes);
|
||||
let pubkey = to_base62(keypair.public_key().as_ref());
|
||||
(privkey, pubkey)
|
||||
}
|
||||
|
||||
fn keypair_from_password(password: &str) -> Ed25519KeyPair {
|
||||
let mut key = [0; 32];
|
||||
pbkdf2::derive(pbkdf2::PBKDF2_HMAC_SHA256, NonZeroU32::new(4096).unwrap(), SALT, password.as_bytes(), &mut key);
|
||||
Ed25519KeyPair::from_seed_unchecked(&key).unwrap()
|
||||
}
|
||||
|
||||
fn parse_keypair(privkey: &str, pubkey: &str) -> Result<Ed25519KeyPair, Error> {
|
||||
let privkey = from_base62(privkey).map_err(|_| Error::InvalidConfig("Failed to parse private key"))?;
|
||||
let pubkey = from_base62(pubkey).map_err(|_| Error::InvalidConfig("Failed to parse public key"))?;
|
||||
let keypair = Ed25519KeyPair::from_seed_and_public_key(&privkey, &pubkey)
|
||||
.map_err(|_| Error::InvalidConfig("Keys rejected by crypto library"))?;
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
fn parse_private_key(privkey: &str) -> Result<Ed25519KeyPair, 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"))?;
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
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 {
|
||||
return Err(Error::InvalidConfig("Failed to parse public key"));
|
||||
}
|
||||
let mut result = [0; ED25519_PUBLIC_KEY_LEN];
|
||||
result.clone_from_slice(&pubkey);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn public_key_from_private_key(privkey: &str) -> Result<String, Error> {
|
||||
let keypair = Self::parse_private_key(privkey)?;
|
||||
Ok(to_base62(keypair.public_key().as_ref()))
|
||||
}
|
||||
|
||||
pub fn peer_instance<P: Payload>(&self, payload: P) -> PeerCrypto<P> {
|
||||
PeerCrypto::new(
|
||||
self.node_id,
|
||||
payload,
|
||||
self.key_pair.clone(),
|
||||
self.trusted_keys.clone(),
|
||||
self.algorithms.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum MessageResult<P: Payload> {
|
||||
Message(u8),
|
||||
Initialized(P),
|
||||
InitializedWithReply(P),
|
||||
Reply,
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct PeerCrypto<P: Payload> {
|
||||
#[allow(dead_code)]
|
||||
node_id: NodeId,
|
||||
init: Option<InitState<P>>,
|
||||
rotation: Option<RotationState>,
|
||||
unencrypted: bool,
|
||||
core: Option<CryptoCore>,
|
||||
rotate_counter: usize,
|
||||
}
|
||||
|
||||
impl<P: Payload> PeerCrypto<P> {
|
||||
pub fn new(
|
||||
node_id: NodeId, init_payload: P, key_pair: Arc<Ed25519KeyPair>, trusted_keys: Arc<[Ed25519PublicKey]>,
|
||||
algorithms: Algorithms,
|
||||
) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
init: Some(InitState::new(node_id, init_payload, key_pair, trusted_keys, algorithms)),
|
||||
rotation: None,
|
||||
unencrypted: false,
|
||||
core: None,
|
||||
rotate_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_init(&mut self) -> Result<&mut InitState<P>, Error> {
|
||||
if let Some(init) = &mut self.init {
|
||||
Ok(init)
|
||||
} else {
|
||||
Err(Error::InvalidCryptoState("Initialization already finished"))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_core(&mut self) -> Result<&mut CryptoCore, Error> {
|
||||
if let Some(core) = &mut self.core {
|
||||
Ok(core)
|
||||
} else {
|
||||
Err(Error::InvalidCryptoState("Crypto core not ready yet"))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rotation(&mut self) -> Result<&mut RotationState, Error> {
|
||||
if let Some(rotation) = &mut self.rotation {
|
||||
Ok(rotation)
|
||||
} else {
|
||||
Err(Error::InvalidCryptoState("Key rotation not initialized"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, out: &mut MsgBuffer) -> Result<(), Error> {
|
||||
let init = self.get_init()?;
|
||||
if init.stage() != init::STAGE_PING {
|
||||
Err(Error::InvalidCryptoState("Initialization already ongoing"))
|
||||
} else {
|
||||
init.send_ping(out);
|
||||
out.prepend_byte(INIT_MESSAGE_FIRST_BYTE);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_init(&self) -> bool {
|
||||
self.init.is_some()
|
||||
}
|
||||
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.core.is_some()
|
||||
}
|
||||
|
||||
pub fn algorithm_name(&self) -> &'static str {
|
||||
if let Some(ref core) = self.core {
|
||||
let algo = core.algorithm();
|
||||
if algo == &aead::CHACHA20_POLY1305 {
|
||||
"CHACHA20"
|
||||
} else if algo == &aead::AES_128_GCM {
|
||||
"AES128"
|
||||
} else if algo == &aead::AES_256_GCM {
|
||||
"AES256"
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
"PLAIN"
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_init_message(&mut self, buffer: &mut MsgBuffer) -> Result<MessageResult<P>, Error> {
|
||||
let result = self.get_init()?.handle_init(buffer)?;
|
||||
if !buffer.is_empty() {
|
||||
buffer.prepend_byte(INIT_MESSAGE_FIRST_BYTE);
|
||||
}
|
||||
match result {
|
||||
InitResult::Continue => Ok(MessageResult::Reply),
|
||||
InitResult::Success { peer_payload, is_initiator } => {
|
||||
self.core = self.get_init()?.take_core();
|
||||
if self.core.is_none() {
|
||||
self.unencrypted = true;
|
||||
}
|
||||
if self.get_init()?.stage() == init::CLOSING {
|
||||
self.init = None
|
||||
}
|
||||
if self.core.is_some() {
|
||||
self.rotation = Some(RotationState::new(!is_initiator, buffer));
|
||||
}
|
||||
if !is_initiator {
|
||||
if self.unencrypted {
|
||||
return Ok(MessageResult::Initialized(peer_payload));
|
||||
}
|
||||
assert!(!buffer.is_empty());
|
||||
buffer.prepend_byte(MESSAGE_TYPE_ROTATION);
|
||||
self.encrypt_message(buffer)?;
|
||||
}
|
||||
Ok(MessageResult::InitializedWithReply(peer_payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_rotate_message(&mut self, data: &[u8]) -> Result<(), Error> {
|
||||
if self.unencrypted {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(rot) = self.get_rotation()?.handle_message(data)? {
|
||||
let core = self.get_core()?;
|
||||
let algo = core.algorithm();
|
||||
let key = LessSafeKey::new(UnboundKey::new(algo, &rot.key[..algo.key_len()]).unwrap());
|
||||
core.rotate_key(key, rot.id, rot.use_for_sending);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
if self.unencrypted {
|
||||
return Ok(());
|
||||
}
|
||||
self.get_core()?.encrypt(buffer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_message(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
// HOT PATH
|
||||
if self.unencrypted {
|
||||
return Ok(());
|
||||
}
|
||||
self.get_core()?.decrypt(buffer)
|
||||
}
|
||||
|
||||
pub fn handle_message(&mut self, buffer: &mut MsgBuffer) -> Result<MessageResult<P>, Error> {
|
||||
// HOT PATH
|
||||
if buffer.is_empty() {
|
||||
return Err(Error::InvalidCryptoState("No message in buffer"));
|
||||
}
|
||||
if is_init_message(buffer.buffer()) {
|
||||
// COLD PATH
|
||||
debug!("Received init message");
|
||||
buffer.take_prefix();
|
||||
self.handle_init_message(buffer)
|
||||
} else {
|
||||
// HOT PATH
|
||||
debug!("Received encrypted message");
|
||||
self.decrypt_message(buffer)?;
|
||||
let msg_type = buffer.take_prefix();
|
||||
if msg_type == MESSAGE_TYPE_ROTATION {
|
||||
// COLD PATH
|
||||
debug!("Received rotation message");
|
||||
self.handle_rotate_message(buffer.buffer())?;
|
||||
buffer.clear();
|
||||
Ok(MessageResult::None)
|
||||
} else {
|
||||
Ok(MessageResult::Message(msg_type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_message(&mut self, type_: u8, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
// HOT PATH
|
||||
assert_ne!(type_, MESSAGE_TYPE_ROTATION);
|
||||
buffer.prepend_byte(type_);
|
||||
self.encrypt_message(buffer)
|
||||
}
|
||||
|
||||
pub fn every_second(&mut self, out: &mut MsgBuffer) -> Result<MessageResult<P>, Error> {
|
||||
out.clear();
|
||||
if let Some(ref mut core) = self.core {
|
||||
core.every_second()
|
||||
}
|
||||
if let Some(ref mut init) = self.init {
|
||||
init.every_second(out)?;
|
||||
}
|
||||
if self.init.as_ref().map(|i| i.stage()).unwrap_or(CLOSING) == CLOSING {
|
||||
self.init = None
|
||||
}
|
||||
if !out.is_empty() {
|
||||
out.prepend_byte(INIT_MESSAGE_FIRST_BYTE);
|
||||
return Ok(MessageResult::Reply);
|
||||
}
|
||||
if let Some(ref mut rotate) = self.rotation {
|
||||
self.rotate_counter += 1;
|
||||
if self.rotate_counter >= ROTATE_INTERVAL {
|
||||
self.rotate_counter = 0;
|
||||
if let Some(rot) = rotate.cycle(out) {
|
||||
let core = self.get_core()?;
|
||||
let algo = core.algorithm();
|
||||
let key = LessSafeKey::new(UnboundKey::new(algo, &rot.key[..algo.key_len()]).unwrap());
|
||||
core.rotate_key(key, rot.id, rot.use_for_sending);
|
||||
}
|
||||
if !out.is_empty() {
|
||||
out.prepend_byte(MESSAGE_TYPE_ROTATION);
|
||||
self.encrypt_message(out)?;
|
||||
return Ok(MessageResult::Reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(MessageResult::None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_init_message(msg: &[u8]) -> bool {
|
||||
// HOT PATH
|
||||
!msg.is_empty() && msg[0] == INIT_MESSAGE_FIRST_BYTE
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::types::NODE_ID_BYTES;
|
||||
|
||||
fn create_node(config: &Config) -> PeerCrypto<Vec<u8>> {
|
||||
let rng = SystemRandom::new();
|
||||
let mut node_id = [0; NODE_ID_BYTES];
|
||||
rng.fill(&mut node_id).unwrap();
|
||||
let crypto = Crypto::new(node_id, config).unwrap();
|
||||
crypto.peer_instance(vec![])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal() {
|
||||
let config = Config { password: Some("test".to_string()), ..Default::default() };
|
||||
let mut node1 = create_node(&config);
|
||||
let mut node2 = create_node(&config);
|
||||
let mut msg = MsgBuffer::new(16);
|
||||
|
||||
node1.initialize(&mut msg).unwrap();
|
||||
assert!(!msg.is_empty());
|
||||
|
||||
debug!("Node1 -> Node2");
|
||||
let res = node2.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::Reply);
|
||||
assert!(!msg.is_empty());
|
||||
|
||||
debug!("Node1 <- Node2");
|
||||
let res = node1.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::InitializedWithReply(vec![]));
|
||||
assert!(!msg.is_empty());
|
||||
|
||||
debug!("Node1 -> Node2");
|
||||
let res = node2.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::InitializedWithReply(vec![]));
|
||||
assert!(!msg.is_empty());
|
||||
|
||||
debug!("Node1 <- Node2");
|
||||
let res = node1.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::None);
|
||||
assert!(msg.is_empty());
|
||||
|
||||
let mut buffer = MsgBuffer::new(16);
|
||||
let rng = SystemRandom::new();
|
||||
buffer.set_length(1000);
|
||||
rng.fill(buffer.message_mut()).unwrap();
|
||||
for _ in 0..1000 {
|
||||
node1.send_message(1, &mut buffer).unwrap();
|
||||
let res = node2.handle_message(&mut buffer).unwrap();
|
||||
assert_eq!(res, MessageResult::Message(1));
|
||||
|
||||
match node1.every_second(&mut msg).unwrap() {
|
||||
MessageResult::None => (),
|
||||
MessageResult::Reply => {
|
||||
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).unwrap() {
|
||||
MessageResult::None => (),
|
||||
MessageResult::Reply => {
|
||||
let res = node1.handle_message(&mut msg).unwrap();
|
||||
assert_eq!(res, MessageResult::None);
|
||||
}
|
||||
other => assert_eq!(other, MessageResult::None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,453 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
// This module implements a crypto core for encrypting and decrypting message streams
|
||||
//
|
||||
// The crypto core only encrypts and decrypts messages, using given keys. Negotiating and rotating the keys is out of
|
||||
// scope of the crypto core. The crypto core assumes that the remote node will always have the necessary key to decrypt
|
||||
// the message.
|
||||
//
|
||||
// The crypto core encrypts messages in place, writes some extra data (key id and nonce) into a given space and
|
||||
// includes the given header data in the authentication tag. When decrypting messages, the crypto core reads the extra
|
||||
// data, uses the key id to find the right key to decrypting the message and then decrypts the message, using the given
|
||||
// nonce and including the given header data in the verification of the authentication tag.
|
||||
//
|
||||
// While the core only uses a single key at a time for encrypting messages, it is ready to decrypt messages based on
|
||||
// one of 4 stored keys (the encryption key being one of them). An external key rotation is responsible for adding the
|
||||
// key to the remote peer before switching to the key on the local peer for encryption.
|
||||
//
|
||||
// As mentioned, the encryption and decryption works in place. Therefore the parameter payload_and_tag contains (when
|
||||
// decrypting) or provides space for (when encrypting) the payload and the authentication tag. When encrypting, that
|
||||
// means, that the last TAG_LEN bytes of payload_and_tag must be reserved for the tag and must not contain payload
|
||||
// bytes.
|
||||
//
|
||||
// The nonce is a value of 12 bytes (192 bits). Since both nodes can use the same key for encryption, the most
|
||||
// significant byte (msb) of the nonce is initialized differently on both peers: one peer uses the value 0x00 and the
|
||||
// other one 0x80. That means that the nonce space is essentially divided in two halves, one for each node.
|
||||
//
|
||||
// To save space and keep the encrypted data aligned to 64 bits, not all bytes of the nonce are transferred. Instead,
|
||||
// only 7 bytes are included in messages (another byte is used for the key id, hence 64 bit alignment). The rest of the
|
||||
// nonce is deduced by the nodes: All other bytes are assumed to be 0x00, except for the most significant byte, which
|
||||
// is assumed to be the opposite ones own msb. This has two nice effects:
|
||||
// 1) Long before the nonce could theoretically repeat, the messages can no longer be decrypted by the peer as the
|
||||
// higher bytes are no longer zero as assumed.
|
||||
// 2) By deducing the msb to be the opposite of ones own msb, it is no longer possible for an attacker to redirect a
|
||||
// message back to the sender because then the assumed nonce will be wrong and the message fails to decrypt. Otherwise,
|
||||
// this could lead to problems as nodes would be able to accidentally decrypt their own messages.
|
||||
//
|
||||
// In order to be resistent against replay attacks but allow for reordering of messages, the crypto core uses nonce
|
||||
// pinning. For every active key, the biggest nonce seen so far is being tracked. Every second, the biggest nonce seen
|
||||
// one second ago plus 1 becomes the minimum nonce that is accepted for that key. That means, that reordering can
|
||||
// happen within one second but after a second, old messages will not be accepted anymore.
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||
use ring::{
|
||||
aead::{self, LessSafeKey, UnboundKey},
|
||||
rand::{SecureRandom, SystemRandom},
|
||||
};
|
||||
|
||||
use std::{
|
||||
io::{Cursor, Read, Write},
|
||||
mem,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{error::Error, util::MsgBuffer};
|
||||
|
||||
const NONCE_LEN: usize = 12;
|
||||
pub const TAG_LEN: usize = 16;
|
||||
pub const EXTRA_LEN: usize = 8;
|
||||
|
||||
fn random_data(size: usize) -> Vec<u8> {
|
||||
let rand = SystemRandom::new();
|
||||
let mut data = vec![0; size];
|
||||
rand.fill(&mut data).expect("Failed to obtain random bytes");
|
||||
data
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, Ord, PartialEq, Debug, Eq, Clone)]
|
||||
struct Nonce([u8; NONCE_LEN]);
|
||||
|
||||
impl Nonce {
|
||||
fn zero() -> Self {
|
||||
Nonce([0; NONCE_LEN])
|
||||
}
|
||||
|
||||
fn random(rand: &SystemRandom) -> Self {
|
||||
let mut nonce = Nonce::zero();
|
||||
rand.fill(&mut nonce.0[6..]).expect("Failed to obtain random bytes");
|
||||
nonce
|
||||
}
|
||||
|
||||
fn set_msb(&mut self, val: u8) {
|
||||
self.0[0] = val
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> &[u8; NONCE_LEN] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn increment(&mut self) {
|
||||
for i in (0..NONCE_LEN).rev() {
|
||||
let mut num = self.0[i];
|
||||
num = num.wrapping_add(1);
|
||||
self.0[i] = num;
|
||||
if num > 0 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CryptoKey {
|
||||
key: LessSafeKey,
|
||||
send_nonce: Nonce,
|
||||
min_nonce: Nonce,
|
||||
next_min_nonce: Nonce,
|
||||
seen_nonce: Nonce,
|
||||
}
|
||||
|
||||
impl CryptoKey {
|
||||
fn new(rand: &SystemRandom, key: LessSafeKey, nonce_half: bool) -> Self {
|
||||
let mut send_nonce = Nonce::random(rand);
|
||||
send_nonce.set_msb(if nonce_half { 0x80 } else { 0x00 });
|
||||
CryptoKey {
|
||||
key,
|
||||
send_nonce,
|
||||
min_nonce: Nonce::zero(),
|
||||
next_min_nonce: Nonce::zero(),
|
||||
seen_nonce: Nonce::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_min_nonce(&mut self) {
|
||||
mem::swap(&mut self.min_nonce, &mut self.next_min_nonce);
|
||||
self.next_min_nonce = self.seen_nonce.clone();
|
||||
self.next_min_nonce.increment();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CryptoCore {
|
||||
rand: SystemRandom,
|
||||
keys: [CryptoKey; 4],
|
||||
current_key: usize,
|
||||
nonce_half: bool,
|
||||
}
|
||||
|
||||
impl CryptoCore {
|
||||
pub fn new(key: LessSafeKey, nonce_half: bool) -> Self {
|
||||
let rand = SystemRandom::new();
|
||||
let dummy_key_data = random_data(key.algorithm().key_len());
|
||||
let dummy_key1 = LessSafeKey::new(UnboundKey::new(key.algorithm(), &dummy_key_data).unwrap());
|
||||
let dummy_key2 = LessSafeKey::new(UnboundKey::new(key.algorithm(), &dummy_key_data).unwrap());
|
||||
let dummy_key3 = LessSafeKey::new(UnboundKey::new(key.algorithm(), &dummy_key_data).unwrap());
|
||||
Self {
|
||||
keys: [
|
||||
CryptoKey::new(&rand, key, nonce_half),
|
||||
CryptoKey::new(&rand, dummy_key1, nonce_half),
|
||||
CryptoKey::new(&rand, dummy_key2, nonce_half),
|
||||
CryptoKey::new(&rand, dummy_key3, nonce_half),
|
||||
],
|
||||
current_key: 0,
|
||||
nonce_half,
|
||||
rand,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, buffer: &mut MsgBuffer) {
|
||||
let data_start = buffer.get_start();
|
||||
let data_length = buffer.len();
|
||||
assert!(buffer.get_start() >= EXTRA_LEN);
|
||||
buffer.set_start(data_start - EXTRA_LEN);
|
||||
buffer.set_length(data_length + EXTRA_LEN + TAG_LEN);
|
||||
let (extra, data_and_tag) = buffer.message_mut().split_at_mut(EXTRA_LEN);
|
||||
let (data, tag_space) = data_and_tag.split_at_mut(data_length);
|
||||
let key = &mut self.keys[self.current_key];
|
||||
key.send_nonce.increment();
|
||||
{
|
||||
let mut extra = Cursor::new(extra);
|
||||
extra.write_u8(self.current_key as u8).unwrap();
|
||||
extra.write_all(&key.send_nonce.as_bytes()[5..]).unwrap();
|
||||
}
|
||||
let nonce = aead::Nonce::assume_unique_for_key(*key.send_nonce.as_bytes());
|
||||
let tag = key.key.seal_in_place_separate_tag(nonce, aead::Aad::empty(), data).expect("Failed to encrypt");
|
||||
tag_space.clone_from_slice(tag.as_ref());
|
||||
}
|
||||
|
||||
fn decrypt_with_key(key: &mut CryptoKey, nonce: Nonce, data_and_tag: &mut [u8]) -> Result<(), Error> {
|
||||
if nonce < key.min_nonce {
|
||||
return Err(Error::Crypto("Old nonce rejected"));
|
||||
}
|
||||
// decrypt
|
||||
let crypto_nonce = aead::Nonce::assume_unique_for_key(*nonce.as_bytes());
|
||||
key.key
|
||||
.open_in_place(crypto_nonce, aead::Aad::empty(), data_and_tag)
|
||||
.map_err(|_| Error::Crypto("Failed to decrypt data"))?;
|
||||
// last seen nonce
|
||||
if key.seen_nonce < nonce {
|
||||
key.seen_nonce = nonce;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrypt(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
assert!(buffer.len() >= EXTRA_LEN + TAG_LEN);
|
||||
let (extra, data_and_tag) = buffer.message_mut().split_at_mut(EXTRA_LEN);
|
||||
let key_id;
|
||||
let mut nonce;
|
||||
{
|
||||
let mut extra = Cursor::new(extra);
|
||||
key_id = extra.read_u8().map_err(|_| Error::Crypto("Input data too short"))? % 4;
|
||||
nonce = Nonce::zero();
|
||||
extra.read_exact(&mut nonce.0[5..]).map_err(|_| Error::Crypto("Input data too short"))?;
|
||||
nonce.set_msb(if self.nonce_half { 0x00 } else { 0x80 });
|
||||
}
|
||||
let key = &mut self.keys[key_id as usize];
|
||||
let result = Self::decrypt_with_key(key, nonce, data_and_tag);
|
||||
buffer.set_start(buffer.get_start() + EXTRA_LEN);
|
||||
buffer.set_length(buffer.len() - TAG_LEN);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn rotate_key(&mut self, key: LessSafeKey, id: u64, use_for_sending: bool) {
|
||||
debug!("Rotated key {} (use for sending: {})", id, use_for_sending);
|
||||
let id = (id % 4) as usize;
|
||||
self.keys[id] = CryptoKey::new(&self.rand, key, self.nonce_half);
|
||||
if use_for_sending {
|
||||
self.current_key = id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn algorithm(&self) -> &'static aead::Algorithm {
|
||||
self.keys[self.current_key].key.algorithm()
|
||||
}
|
||||
|
||||
pub fn every_second(&mut self) {
|
||||
// Set min nonce on all keys
|
||||
for k in &mut self.keys {
|
||||
k.update_min_nonce();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_dummy_pair(algo: &'static aead::Algorithm) -> (CryptoCore, CryptoCore) {
|
||||
let key_data = random_data(algo.key_len());
|
||||
let sender = CryptoCore::new(LessSafeKey::new(UnboundKey::new(algo, &key_data).unwrap()), true);
|
||||
let receiver = CryptoCore::new(LessSafeKey::new(UnboundKey::new(algo, &key_data).unwrap()), false);
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
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 mut iterations = 0;
|
||||
let start = Instant::now();
|
||||
while (Instant::now() - start).as_nanos() < max_time.as_nanos() {
|
||||
for _ in 0..1000 {
|
||||
sender.encrypt(&mut buffer);
|
||||
receiver.decrypt(&mut buffer).unwrap();
|
||||
}
|
||||
iterations += 1000;
|
||||
}
|
||||
let duration = (Instant::now() - start).as_secs_f64();
|
||||
let data = iterations * 1000 * 2;
|
||||
data as f64 / duration / 1_000_000.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ring::aead::{self, LessSafeKey, UnboundKey};
|
||||
|
||||
#[test]
|
||||
fn test_nonce() {
|
||||
let mut nonce = Nonce::zero();
|
||||
assert_eq!(nonce.as_bytes(), &[0; 12]);
|
||||
nonce.increment();
|
||||
assert_eq!(nonce.as_bytes(), &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
nonce.increment();
|
||||
assert_eq!(nonce.as_bytes(), &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
|
||||
}
|
||||
|
||||
fn test_encrypt_decrypt(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
assert_eq!(&plain[..], buffer.message());
|
||||
sender.encrypt(&mut buffer);
|
||||
assert_ne!(&plain[..], buffer.message());
|
||||
receiver.decrypt(&mut buffer).unwrap();
|
||||
assert_eq!(&plain[..], buffer.message());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_aes128() {
|
||||
test_encrypt_decrypt(&aead::AES_128_GCM)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_aes256() {
|
||||
test_encrypt_decrypt(&aead::AES_256_GCM)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_chacha() {
|
||||
test_encrypt_decrypt(&aead::CHACHA20_POLY1305)
|
||||
}
|
||||
|
||||
fn test_tampering(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
sender.encrypt(&mut buffer);
|
||||
let mut d = buffer.clone();
|
||||
assert!(receiver.decrypt(&mut d,).is_ok());
|
||||
// Tamper with extra data byte 1 (subkey id)
|
||||
d = buffer.clone();
|
||||
d.message_mut()[0] ^= 1;
|
||||
assert!(receiver.decrypt(&mut d).is_err());
|
||||
// Tamper with extra data byte 2 (nonce)
|
||||
d = buffer.clone();
|
||||
d.message_mut()[1] ^= 1;
|
||||
assert!(receiver.decrypt(&mut d).is_err());
|
||||
// Tamper with data itself
|
||||
d = buffer.clone();
|
||||
d.message_mut()[EXTRA_LEN] ^= 1;
|
||||
assert!(receiver.decrypt(&mut d).is_err());
|
||||
// Check everything still works
|
||||
d = buffer;
|
||||
assert!(receiver.decrypt(&mut d).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tampering_aes128() {
|
||||
test_tampering(&aead::AES_128_GCM)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tampering_aes256() {
|
||||
test_tampering(&aead::AES_256_GCM)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tampering_chacha() {
|
||||
test_tampering(&aead::CHACHA20_POLY1305)
|
||||
}
|
||||
|
||||
fn test_nonce_pinning(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
sender.encrypt(&mut buffer);
|
||||
{
|
||||
let mut d = buffer.clone();
|
||||
assert!(receiver.decrypt(&mut d).is_ok());
|
||||
}
|
||||
receiver.every_second();
|
||||
{
|
||||
let mut d = buffer.clone();
|
||||
assert!(receiver.decrypt(&mut d).is_ok());
|
||||
}
|
||||
receiver.every_second();
|
||||
{
|
||||
let mut d = buffer;
|
||||
assert!(receiver.decrypt(&mut d).is_err());
|
||||
}
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
sender.encrypt(&mut buffer);
|
||||
assert!(receiver.decrypt(&mut buffer).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_pinning_aes128() {
|
||||
test_nonce_pinning(&aead::AES_128_GCM)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_pinning_aes256() {
|
||||
test_nonce_pinning(&aead::AES_256_GCM)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_pinning_chacha() {
|
||||
test_nonce_pinning(&aead::CHACHA20_POLY1305)
|
||||
}
|
||||
|
||||
fn test_key_rotation(algo: &'static aead::Algorithm) {
|
||||
let (mut sender, mut receiver) = create_dummy_pair(algo);
|
||||
let plain = random_data(1000);
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
buffer.clone_from(&plain);
|
||||
|
||||
sender.encrypt(&mut buffer);
|
||||
assert!(receiver.decrypt(&mut buffer).is_ok());
|
||||
|
||||
let new_key = random_data(algo.key_len());
|
||||
receiver.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 1, false);
|
||||
receiver.encrypt(&mut buffer);
|
||||
assert!(sender.decrypt(&mut buffer).is_ok());
|
||||
sender.encrypt(&mut buffer);
|
||||
assert!(receiver.decrypt(&mut buffer).is_ok());
|
||||
sender.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 1, true);
|
||||
receiver.encrypt(&mut buffer);
|
||||
assert!(sender.decrypt(&mut buffer).is_ok());
|
||||
sender.encrypt(&mut buffer);
|
||||
assert!(receiver.decrypt(&mut buffer).is_ok());
|
||||
let new_key = random_data(algo.key_len());
|
||||
sender.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 2, true);
|
||||
sender.encrypt(&mut buffer);
|
||||
assert!(receiver.decrypt(&mut buffer).is_err());
|
||||
receiver.encrypt(&mut buffer);
|
||||
assert!(sender.decrypt(&mut buffer).is_ok());
|
||||
|
||||
receiver.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 2, false);
|
||||
receiver.encrypt(&mut buffer);
|
||||
assert!(sender.decrypt(&mut buffer).is_ok());
|
||||
sender.encrypt(&mut buffer);
|
||||
assert!(receiver.decrypt(&mut buffer).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_rotation_aes128() {
|
||||
test_key_rotation(&aead::AES_128_GCM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_rotation_aes256() {
|
||||
test_key_rotation(&aead::AES_256_GCM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_rotation_chacha() {
|
||||
test_key_rotation(&aead::CHACHA20_POLY1305);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_size() {
|
||||
assert_eq!(2384, mem::size_of::<CryptoCore>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_speed_aes128() {
|
||||
let speed = test_speed(&aead::AES_128_GCM, &Duration::from_secs_f32(0.2));
|
||||
assert!(speed > 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_speed_aes256() {
|
||||
let speed = test_speed(&aead::AES_256_GCM, &Duration::from_secs_f32(0.2));
|
||||
assert!(speed > 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_speed_chacha() {
|
||||
let speed = test_speed(&aead::CHACHA20_POLY1305, &Duration::from_secs_f32(0.2));
|
||||
assert!(speed > 10.0);
|
||||
}
|
||||
}
|
|
@ -1,929 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
// This module implements a 3-way handshake to initialize an authenticated and encrypted connection.
|
||||
//
|
||||
// The handshake assumes that each node has a asymmetric Curve 25519 key pair as well as a list of trusted public keys
|
||||
// and a set of supported crypto algorithms as well as the expected speed when using them. If successful, the handshake
|
||||
// will negotiate a crypto algorithm to use and a common ephemeral symmetric key and exchange a given payload between
|
||||
// the nodes.
|
||||
//
|
||||
// The handshake consists of 3 stages, "ping", "pong" and "peng". In the following description, the node that initiates
|
||||
// the connection is named "A" and the other node is named "B". Since a lot of things are going on in parallel in the
|
||||
// handshake, those aspects are described separately in the following paragraphs.
|
||||
//
|
||||
// Every message contains the node id of the sender. If a node receives a message with its own node id, it just ignores
|
||||
// it and closes the connection. This is the way nodes avoid to connect to themselves as it is not trivial for a node
|
||||
// to know its own addresses (especially in the case of NAT).
|
||||
//
|
||||
// All initialization messages are signed by the asymmetric key of the sender. Also the messages indicate the public
|
||||
// key being used, so the receiver can use the correct public key to verify the signature. The public key itself is not
|
||||
// attached to the message for privacy reasons (the public key is stable over multiple restarts while the node id is
|
||||
// only valid for a single run). Instead, a 2 byte salt value as well as the last 2 bytes of the salted sha 2 hash of
|
||||
// the public key are used to identify the public key. This way, a receiver that trusts this public key can identify
|
||||
// it but a random observer can't. If the public key is unknown or the signature can't be verified, the message is
|
||||
// ignored.
|
||||
//
|
||||
// Every message contains a byte that specifies the stage (ping = 1, pong = 2, peng = 3). If a message with an
|
||||
// unexpected stage is received, it is ignored and the last message that has been sent is repeated. There is only one
|
||||
// exception to this rule: if a "pong" message is expected, but a "ping" message is received instead AND the node id of
|
||||
// the sender is greater than the node id of the receiver, the receiving node will reset its state and assume the role
|
||||
// of a receiver of the initialization (i.e. "B"). This is used to "negotiate" the roles A and B when both nodes
|
||||
// initiate the connection in parallel and think they are A.
|
||||
//
|
||||
// Upon connection creation, both nodes create a random ephemeral ECDH key pair and exchange the public keys in the
|
||||
// ping and pong messages. A sends the ping message to B containing A's public key and B replies with a pong message
|
||||
// containing B's public key. That means, that after receiving the ping message B can calculate the shared key material
|
||||
// and after receiving the pong message A can calculate the shared key material.
|
||||
//
|
||||
// The ping message and the pong message contain a set of supported crypto algorithms together with the estimated
|
||||
// speeds of the algorithms. When B receives a ping message, or A receives a pong message, it can combine this
|
||||
// information with its own algorithm list and select the algorithm with the best expected speed for the crypto core.
|
||||
//
|
||||
// The pong and peng message contain the payload that the nodes want to exchange in the initialization phase apart from
|
||||
// the cryptographic initialization. This payload is encoded according to the application and encrypted using the key
|
||||
// material and the crypto algorithm that have been negotiated via the ping and pong messages. The pong message,
|
||||
// therefore contains information to set up symmetric encryption as well as a part that is already encrypted.
|
||||
//
|
||||
// The handshake ends for A after sending the peng message and for B after receiving this message. At this time both
|
||||
// nodes initialize the connection using the payload and enter normal operation. The negotiated crypto core is used for
|
||||
// future communication and the key rotation is started. Since the peng message can be lost, A needs to keep the
|
||||
// initialization state in order to repeat a lost peng message. After one second, A removes that state.
|
||||
//
|
||||
// Once every second, both nodes check whether they have already finished the initialization. If not, they repeat their
|
||||
// last message. After 5 seconds, the initialization is aborted as failed.
|
||||
|
||||
use super::{
|
||||
core::{CryptoCore, EXTRA_LEN},
|
||||
Algorithms, EcdhPrivateKey, EcdhPublicKey, Ed25519PublicKey, Payload,
|
||||
};
|
||||
use crate::{error::Error, types::NodeId, util::MsgBuffer};
|
||||
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
|
||||
use ring::{
|
||||
aead::{Algorithm, LessSafeKey, UnboundKey, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305},
|
||||
agreement::{agree_ephemeral, X25519},
|
||||
digest,
|
||||
rand::{SecureRandom, SystemRandom},
|
||||
signature::{self, Ed25519KeyPair, KeyPair, ED25519, ED25519_PUBLIC_KEY_LEN},
|
||||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{
|
||||
cmp, f32,
|
||||
fmt::Debug,
|
||||
io::{self, Cursor, Read, Write},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub const STAGE_PING: u8 = 1;
|
||||
pub const STAGE_PONG: u8 = 2;
|
||||
pub const STAGE_PENG: u8 = 3;
|
||||
pub const WAITING_TO_CLOSE: u8 = 4;
|
||||
pub const CLOSING: u8 = 5;
|
||||
|
||||
pub const MAX_FAILED_RETRIES: usize = 120;
|
||||
|
||||
pub const SALTED_NODE_ID_HASH_LEN: usize = 20;
|
||||
pub type SaltedNodeIdHash = [u8; SALTED_NODE_ID_HASH_LEN];
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum InitMsg {
|
||||
Ping {
|
||||
salted_node_id_hash: SaltedNodeIdHash,
|
||||
ecdh_public_key: EcdhPublicKey,
|
||||
algorithms: Algorithms,
|
||||
},
|
||||
Pong {
|
||||
salted_node_id_hash: SaltedNodeIdHash,
|
||||
ecdh_public_key: EcdhPublicKey,
|
||||
algorithms: Algorithms,
|
||||
encrypted_payload: MsgBuffer,
|
||||
},
|
||||
Peng {
|
||||
salted_node_id_hash: SaltedNodeIdHash,
|
||||
encrypted_payload: MsgBuffer,
|
||||
},
|
||||
}
|
||||
|
||||
impl InitMsg {
|
||||
const PART_ALGORITHMS: u8 = 4;
|
||||
const PART_ECDH_PUBLIC_KEY: u8 = 3;
|
||||
const PART_END: u8 = 0;
|
||||
const PART_PAYLOAD: u8 = 5;
|
||||
const PART_SALTED_NODE_ID_HASH: u8 = 2;
|
||||
const PART_STAGE: u8 = 1;
|
||||
|
||||
fn stage(&self) -> u8 {
|
||||
match self {
|
||||
InitMsg::Ping { .. } => STAGE_PING,
|
||||
InitMsg::Pong { .. } => STAGE_PONG,
|
||||
InitMsg::Peng { .. } => STAGE_PENG,
|
||||
}
|
||||
}
|
||||
|
||||
fn salted_node_id_hash(&self) -> &SaltedNodeIdHash {
|
||||
match self {
|
||||
InitMsg::Ping { salted_node_id_hash, .. }
|
||||
| InitMsg::Pong { salted_node_id_hash, .. }
|
||||
| InitMsg::Peng { salted_node_id_hash, .. } => salted_node_id_hash,
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_hash(key: &Ed25519PublicKey, salt: &[u8; 4]) -> [u8; 4] {
|
||||
let mut data = [0; ED25519_PUBLIC_KEY_LEN + 4];
|
||||
data[..ED25519_PUBLIC_KEY_LEN].clone_from_slice(key);
|
||||
data[ED25519_PUBLIC_KEY_LEN..].clone_from_slice(salt);
|
||||
let hash = digest::digest(&digest::SHA256, &data);
|
||||
let mut short_hash = [0; 4];
|
||||
short_hash.clone_from_slice(&hash.as_ref()[..4]);
|
||||
short_hash
|
||||
}
|
||||
|
||||
fn read_from(buffer: &[u8], trusted_keys: &[Ed25519PublicKey]) -> Result<(Self, Ed25519PublicKey), Error> {
|
||||
let mut r = Cursor::new(buffer);
|
||||
|
||||
let mut public_key_salt = [0; 4];
|
||||
r.read_exact(&mut public_key_salt).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
let mut public_key_hash = [0; 4];
|
||||
r.read_exact(&mut public_key_hash).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
let mut public_key_data = [0; ED25519_PUBLIC_KEY_LEN];
|
||||
let mut found_key = false;
|
||||
for tk in trusted_keys {
|
||||
if Self::calculate_hash(tk, &public_key_salt) == public_key_hash {
|
||||
public_key_data.clone_from_slice(tk);
|
||||
found_key = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found_key {
|
||||
return Err(Error::Crypto("untrusted peer"));
|
||||
}
|
||||
|
||||
let mut stage = None;
|
||||
let mut salted_node_id_hash = None;
|
||||
let mut ecdh_public_key = None;
|
||||
let mut encrypted_payload = None;
|
||||
let mut algorithms = None;
|
||||
|
||||
loop {
|
||||
let field = r.read_u8().map_err(|_| Error::Parse("Init message too short"))?;
|
||||
if field == Self::PART_END {
|
||||
break;
|
||||
}
|
||||
let field_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Parse("Init message too short"))? as usize;
|
||||
match field {
|
||||
Self::PART_STAGE => {
|
||||
if field_len != 1 {
|
||||
return Err(Error::CryptoInit("Invalid size for stage field"));
|
||||
}
|
||||
stage = Some(r.read_u8().map_err(|_| Error::Parse("Init message too short"))?)
|
||||
}
|
||||
Self::PART_SALTED_NODE_ID_HASH => {
|
||||
if field_len != SALTED_NODE_ID_HASH_LEN {
|
||||
return Err(Error::CryptoInit("Invalid size for salted node id hash field"));
|
||||
}
|
||||
let mut id = [0; SALTED_NODE_ID_HASH_LEN];
|
||||
r.read_exact(&mut id).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
salted_node_id_hash = Some(id)
|
||||
}
|
||||
Self::PART_ECDH_PUBLIC_KEY => {
|
||||
let mut pub_key_data = smallvec![0; field_len];
|
||||
r.read_exact(&mut pub_key_data).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
ecdh_public_key = Some(EcdhPublicKey::new(&X25519, pub_key_data));
|
||||
}
|
||||
Self::PART_PAYLOAD => {
|
||||
let mut payload = MsgBuffer::new(0);
|
||||
payload.set_length(field_len);
|
||||
r.read_exact(payload.message_mut()).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
encrypted_payload = Some(payload);
|
||||
}
|
||||
Self::PART_ALGORITHMS => {
|
||||
let count = field_len / 5;
|
||||
let mut algos = SmallVec::with_capacity(count);
|
||||
let mut allow_unencrypted = false;
|
||||
for _ in 0..count {
|
||||
let algo = match r.read_u8().map_err(|_| Error::Parse("Init message too short"))? {
|
||||
0 => {
|
||||
allow_unencrypted = true;
|
||||
None
|
||||
}
|
||||
1 => Some(&AES_128_GCM),
|
||||
2 => Some(&AES_256_GCM),
|
||||
3 => Some(&CHACHA20_POLY1305),
|
||||
_ => None,
|
||||
};
|
||||
let speed =
|
||||
r.read_f32::<NetworkEndian>().map_err(|_| Error::Parse("Init message too short"))?;
|
||||
if let Some(algo) = algo {
|
||||
algos.push((algo, speed));
|
||||
}
|
||||
}
|
||||
algorithms = Some(Algorithms { algorithm_speeds: algos, allow_unencrypted });
|
||||
}
|
||||
_ => {
|
||||
let mut data = vec![0; field_len];
|
||||
r.read_exact(&mut data).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pos = r.position() as usize;
|
||||
|
||||
let signature_len = r.read_u8().map_err(|_| Error::Parse("Init message too short"))? as usize;
|
||||
let mut signature: SmallVec<[u8; 32]> = smallvec![0; signature_len];
|
||||
r.read_exact(&mut signature).map_err(|_| Error::Parse("Init message too short"))?;
|
||||
|
||||
let signed_data = &r.into_inner()[0..pos];
|
||||
let public_key = signature::UnparsedPublicKey::new(&ED25519, &public_key_data);
|
||||
if public_key.verify(signed_data, &signature).is_err() {
|
||||
return Err(Error::Crypto("invalid signature"));
|
||||
}
|
||||
|
||||
let stage = match stage {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without stage")),
|
||||
};
|
||||
let salted_node_id_hash = match salted_node_id_hash {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without node id")),
|
||||
};
|
||||
|
||||
let msg = match stage {
|
||||
STAGE_PING => {
|
||||
let ecdh_public_key = match ecdh_public_key {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without ecdh public key")),
|
||||
};
|
||||
let algorithms = match algorithms {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without algorithms")),
|
||||
};
|
||||
Self::Ping { salted_node_id_hash, ecdh_public_key, algorithms }
|
||||
}
|
||||
STAGE_PONG => {
|
||||
let ecdh_public_key = match ecdh_public_key {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without ecdh public key")),
|
||||
};
|
||||
let algorithms = match algorithms {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without algorithms")),
|
||||
};
|
||||
let encrypted_payload = match encrypted_payload {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without payload")),
|
||||
};
|
||||
Self::Pong { salted_node_id_hash, ecdh_public_key, algorithms, encrypted_payload }
|
||||
}
|
||||
STAGE_PENG => {
|
||||
let encrypted_payload = match encrypted_payload {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::CryptoInit("Init message without payload")),
|
||||
};
|
||||
Self::Peng { salted_node_id_hash, encrypted_payload }
|
||||
}
|
||||
_ => return Err(Error::CryptoInit("Invalid stage")),
|
||||
};
|
||||
|
||||
Ok((msg, public_key_data))
|
||||
}
|
||||
|
||||
fn write_to(&self, buffer: &mut [u8], key: &Ed25519KeyPair) -> Result<usize, io::Error> {
|
||||
let mut w = Cursor::new(buffer);
|
||||
|
||||
let rand = SystemRandom::new();
|
||||
let mut salt = [0; 4];
|
||||
rand.fill(&mut salt).unwrap();
|
||||
let mut public_key = [0; ED25519_PUBLIC_KEY_LEN];
|
||||
public_key.clone_from_slice(key.public_key().as_ref());
|
||||
let hash = Self::calculate_hash(&public_key, &salt);
|
||||
w.write_all(&salt)?;
|
||||
w.write_all(&hash)?;
|
||||
|
||||
w.write_u8(Self::PART_STAGE)?;
|
||||
w.write_u16::<NetworkEndian>(1)?;
|
||||
w.write_u8(self.stage())?;
|
||||
|
||||
match &self {
|
||||
Self::Ping { salted_node_id_hash, .. }
|
||||
| Self::Pong { salted_node_id_hash, .. }
|
||||
| Self::Peng { salted_node_id_hash, .. } => {
|
||||
w.write_u8(Self::PART_SALTED_NODE_ID_HASH)?;
|
||||
w.write_u16::<NetworkEndian>(SALTED_NODE_ID_HASH_LEN as u16)?;
|
||||
w.write_all(salted_node_id_hash)?;
|
||||
}
|
||||
}
|
||||
|
||||
match &self {
|
||||
Self::Ping { ecdh_public_key, .. } | Self::Pong { ecdh_public_key, .. } => {
|
||||
w.write_u8(Self::PART_ECDH_PUBLIC_KEY)?;
|
||||
let key_bytes = ecdh_public_key.bytes();
|
||||
w.write_u16::<NetworkEndian>(key_bytes.len() as u16)?;
|
||||
w.write_all(key_bytes)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match &self {
|
||||
Self::Ping { algorithms, .. } | Self::Pong { algorithms, .. } => {
|
||||
w.write_u8(Self::PART_ALGORITHMS)?;
|
||||
let mut len = algorithms.algorithm_speeds.len() * 5;
|
||||
if algorithms.allow_unencrypted {
|
||||
len += 5;
|
||||
}
|
||||
w.write_u16::<NetworkEndian>(len as u16)?;
|
||||
if algorithms.allow_unencrypted {
|
||||
w.write_u8(0)?;
|
||||
w.write_f32::<NetworkEndian>(f32::INFINITY)?;
|
||||
}
|
||||
for (algo, speed) in &algorithms.algorithm_speeds {
|
||||
if *algo == &AES_128_GCM {
|
||||
w.write_u8(1)?;
|
||||
} else if *algo == &AES_256_GCM {
|
||||
w.write_u8(2)?;
|
||||
} else if *algo == &CHACHA20_POLY1305 {
|
||||
w.write_u8(3)?;
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
w.write_f32::<NetworkEndian>(*speed)?;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match &self {
|
||||
Self::Pong { encrypted_payload, .. } | Self::Peng { encrypted_payload, .. } => {
|
||||
w.write_u8(Self::PART_PAYLOAD)?;
|
||||
w.write_u16::<NetworkEndian>(encrypted_payload.len() as u16)?;
|
||||
w.write_all(encrypted_payload.message())?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
w.write_u8(Self::PART_END)?;
|
||||
|
||||
let pos = w.position() as usize;
|
||||
let signature = key.sign(&w.get_ref()[0..pos]);
|
||||
w.write_u8(signature.as_ref().len() as u8)?;
|
||||
w.write_all(signature.as_ref())?;
|
||||
|
||||
Ok(w.position() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum InitResult<P: Payload> {
|
||||
Continue,
|
||||
Success { peer_payload: P, is_initiator: bool },
|
||||
}
|
||||
|
||||
pub struct InitState<P: Payload> {
|
||||
node_id: NodeId,
|
||||
salted_node_id_hash: SaltedNodeIdHash,
|
||||
payload: P,
|
||||
key_pair: Arc<Ed25519KeyPair>,
|
||||
trusted_keys: Arc<[Ed25519PublicKey]>,
|
||||
ecdh_private_key: Option<EcdhPrivateKey>,
|
||||
next_stage: u8,
|
||||
close_time: usize,
|
||||
last_message: Option<Vec<u8>>,
|
||||
crypto: Option<CryptoCore>,
|
||||
algorithms: Algorithms,
|
||||
#[allow(dead_code)] // Used in tests
|
||||
selected_algorithm: Option<&'static Algorithm>,
|
||||
failed_retries: usize,
|
||||
}
|
||||
|
||||
impl<P: Payload> InitState<P> {
|
||||
pub fn new(
|
||||
node_id: NodeId, payload: P, key_pair: Arc<Ed25519KeyPair>, trusted_keys: Arc<[Ed25519PublicKey]>,
|
||||
algorithms: Algorithms,
|
||||
) -> Self {
|
||||
let mut hash = [0; SALTED_NODE_ID_HASH_LEN];
|
||||
let rng = SystemRandom::new();
|
||||
rng.fill(&mut hash[0..4]).unwrap();
|
||||
hash[4..].clone_from_slice(&node_id);
|
||||
let d = digest::digest(&digest::SHA256, &hash);
|
||||
hash[4..].clone_from_slice(&d.as_ref()[..16]);
|
||||
Self {
|
||||
node_id,
|
||||
salted_node_id_hash: hash,
|
||||
payload,
|
||||
key_pair,
|
||||
trusted_keys,
|
||||
next_stage: STAGE_PING,
|
||||
last_message: None,
|
||||
crypto: None,
|
||||
ecdh_private_key: None,
|
||||
selected_algorithm: None,
|
||||
algorithms,
|
||||
failed_retries: 0,
|
||||
close_time: 60,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_ping(&mut self, out: &mut MsgBuffer) {
|
||||
// create ecdh ephemeral key
|
||||
let (ecdh_private_key, ecdh_public_key) = self.create_ecdh_keypair();
|
||||
self.ecdh_private_key = Some(ecdh_private_key);
|
||||
|
||||
// create stage 1 msg
|
||||
self.send_message(STAGE_PING, Some(ecdh_public_key), out);
|
||||
|
||||
self.next_stage = STAGE_PONG;
|
||||
}
|
||||
|
||||
pub fn stage(&self) -> u8 {
|
||||
self.next_stage
|
||||
}
|
||||
|
||||
pub fn every_second(&mut self, out: &mut MsgBuffer) -> Result<(), Error> {
|
||||
if self.next_stage == WAITING_TO_CLOSE {
|
||||
if self.close_time == 0 {
|
||||
self.next_stage = CLOSING;
|
||||
} else {
|
||||
self.close_time -= 1;
|
||||
}
|
||||
Ok(())
|
||||
} else if self.next_stage == CLOSING {
|
||||
Ok(())
|
||||
} else if self.failed_retries < MAX_FAILED_RETRIES {
|
||||
self.failed_retries += 1;
|
||||
self.repeat_last_message(out);
|
||||
Ok(())
|
||||
} else {
|
||||
self.next_stage = CLOSING;
|
||||
Err(Error::CryptoInitFatal("Initialization timeout"))
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_master_key(&self, algo: &'static Algorithm, privk: EcdhPrivateKey, pubk: &EcdhPublicKey) -> LessSafeKey {
|
||||
agree_ephemeral(privk, pubk, |k| {
|
||||
UnboundKey::new(algo, &k[..algo.key_len()]).map(LessSafeKey::new).unwrap()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_ecdh_keypair(&self) -> (EcdhPrivateKey, EcdhPublicKey) {
|
||||
let rand = SystemRandom::new();
|
||||
let ecdh_private_key = EcdhPrivateKey::generate(&X25519, &rand).unwrap();
|
||||
let public_key = ecdh_private_key.compute_public_key().unwrap();
|
||||
let mut vec = SmallVec::<[u8; 96]>::new();
|
||||
vec.extend_from_slice(public_key.as_ref());
|
||||
let ecdh_public_key = EcdhPublicKey::new(&X25519, vec);
|
||||
(ecdh_private_key, ecdh_public_key)
|
||||
}
|
||||
|
||||
fn encrypt_payload(&mut self) -> MsgBuffer {
|
||||
let mut buffer = MsgBuffer::new(EXTRA_LEN);
|
||||
self.payload.write_to(&mut buffer);
|
||||
if let Some(crypto) = &mut self.crypto {
|
||||
crypto.encrypt(&mut buffer);
|
||||
}
|
||||
buffer
|
||||
}
|
||||
|
||||
fn decrypt(&mut self, data: &mut MsgBuffer) -> Result<P, Error> {
|
||||
if let Some(crypto) = &mut self.crypto {
|
||||
crypto.decrypt(data)?;
|
||||
}
|
||||
P::read_from(Cursor::new(data.message()))
|
||||
}
|
||||
|
||||
fn check_salted_node_id_hash(&self, hash: &SaltedNodeIdHash, node_id: NodeId) -> bool {
|
||||
let mut h2 = [0; SALTED_NODE_ID_HASH_LEN];
|
||||
h2[0..4].clone_from_slice(&hash[0..4]);
|
||||
h2[4..].clone_from_slice(&node_id);
|
||||
let d = digest::digest(&digest::SHA256, &h2);
|
||||
hash == d.as_ref()
|
||||
}
|
||||
|
||||
fn send_message(&mut self, stage: u8, ecdh_public_key: Option<EcdhPublicKey>, out: &mut MsgBuffer) {
|
||||
debug!("Sending init with stage={}", stage);
|
||||
assert!(out.is_empty());
|
||||
let mut public_key = [0; ED25519_PUBLIC_KEY_LEN];
|
||||
public_key.clone_from_slice(self.key_pair.as_ref().public_key().as_ref());
|
||||
let msg = match stage {
|
||||
STAGE_PING => InitMsg::Ping {
|
||||
salted_node_id_hash: self.salted_node_id_hash,
|
||||
ecdh_public_key: ecdh_public_key.unwrap(),
|
||||
algorithms: self.algorithms.clone(),
|
||||
},
|
||||
STAGE_PONG => InitMsg::Pong {
|
||||
salted_node_id_hash: self.salted_node_id_hash,
|
||||
ecdh_public_key: ecdh_public_key.unwrap(),
|
||||
algorithms: self.algorithms.clone(),
|
||||
encrypted_payload: self.encrypt_payload(),
|
||||
},
|
||||
STAGE_PENG => InitMsg::Peng {
|
||||
salted_node_id_hash: self.salted_node_id_hash,
|
||||
encrypted_payload: self.encrypt_payload(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let bytes = out.buffer();
|
||||
let len = msg.write_to(bytes, &self.key_pair).expect("Buffer too small");
|
||||
self.last_message = Some(bytes[0..len].to_vec());
|
||||
out.set_length(len);
|
||||
}
|
||||
|
||||
fn repeat_last_message(&self, out: &mut MsgBuffer) {
|
||||
if let Some(ref bytes) = self.last_message {
|
||||
debug!("Repeating last init message");
|
||||
let buffer = out.buffer();
|
||||
buffer[0..bytes.len()].copy_from_slice(bytes);
|
||||
out.set_length(bytes.len());
|
||||
}
|
||||
}
|
||||
|
||||
fn select_algorithm(&self, peer_algos: &Algorithms) -> Result<Option<(&'static Algorithm, f32)>, Error> {
|
||||
if self.algorithms.allow_unencrypted && peer_algos.allow_unencrypted {
|
||||
return Ok(None);
|
||||
}
|
||||
// For each supported algorithm, find the algorithm in the list of the peer (ignore algorithm if not found).
|
||||
// Take the minimal speed reported by either us or the peer.
|
||||
// Select the algorithm with the greatest minimal speed.
|
||||
let algo = self
|
||||
.algorithms
|
||||
.algorithm_speeds
|
||||
.iter()
|
||||
.filter_map(|(a1, s1)| {
|
||||
peer_algos
|
||||
.algorithm_speeds
|
||||
.iter()
|
||||
.find(|(a2, _)| a1 == a2)
|
||||
.map(|(_, s2)| (*a1, if s1 < s2 { *s1 } else { *s2 }))
|
||||
})
|
||||
.max_by(|(_, s1), (_, s2)| if s1 < s2 { cmp::Ordering::Less } else { cmp::Ordering::Greater });
|
||||
if let Some(algo) = algo {
|
||||
debug!("Init: best algorithm is {:?} with speed {}", algo.0, algo.1);
|
||||
Ok(Some(algo))
|
||||
} else {
|
||||
Err(Error::CryptoInitFatal("No common algorithms"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_init(&mut self, out: &mut MsgBuffer) -> Result<InitResult<P>, Error> {
|
||||
let (msg, _peer_key) = InitMsg::read_from(out.buffer(), &self.trusted_keys)?;
|
||||
out.clear();
|
||||
let stage = msg.stage();
|
||||
let salted_node_id_hash = *msg.salted_node_id_hash();
|
||||
debug!("Received init with stage={}, expected stage={}", stage, self.next_stage);
|
||||
if self.salted_node_id_hash == salted_node_id_hash
|
||||
|| self.check_salted_node_id_hash(&salted_node_id_hash, self.node_id)
|
||||
{
|
||||
return Err(Error::CryptoInitFatal("Connected to self"));
|
||||
}
|
||||
if stage != self.next_stage {
|
||||
if self.next_stage == STAGE_PONG && stage == STAGE_PING {
|
||||
// special case for concurrent init messages in both directions
|
||||
// the node with the higher node_id "wins" and gets to initialize the connection
|
||||
if salted_node_id_hash > self.salted_node_id_hash {
|
||||
// reset to initial state
|
||||
self.next_stage = STAGE_PING;
|
||||
self.last_message = None;
|
||||
self.ecdh_private_key = None;
|
||||
} else {
|
||||
return Ok(InitResult::Continue);
|
||||
}
|
||||
} else if self.next_stage == CLOSING {
|
||||
return Ok(InitResult::Continue);
|
||||
} else if self.last_message.is_some() {
|
||||
self.repeat_last_message(out);
|
||||
return Ok(InitResult::Continue);
|
||||
} else {
|
||||
return Err(Error::CryptoInitFatal("Received invalid stage as first message"));
|
||||
}
|
||||
}
|
||||
self.failed_retries = 0;
|
||||
match msg {
|
||||
InitMsg::Ping { ecdh_public_key, algorithms, .. } => {
|
||||
// create ecdh ephemeral key
|
||||
let (my_ecdh_private_key, my_ecdh_public_key) = self.create_ecdh_keypair();
|
||||
|
||||
// do ecdh agreement and derive master key
|
||||
let algorithm = self.select_algorithm(&algorithms)?;
|
||||
self.selected_algorithm = algorithm.map(|a| a.0);
|
||||
if let Some((algorithm, _speed)) = algorithm {
|
||||
let master_key = self.derive_master_key(algorithm, my_ecdh_private_key, &ecdh_public_key);
|
||||
self.crypto = Some(CryptoCore::new(master_key, self.salted_node_id_hash > salted_node_id_hash));
|
||||
}
|
||||
|
||||
// create and send stage 2 reply
|
||||
self.send_message(STAGE_PONG, Some(my_ecdh_public_key), out);
|
||||
|
||||
self.next_stage = STAGE_PENG;
|
||||
Ok(InitResult::Continue)
|
||||
}
|
||||
InitMsg::Pong { ecdh_public_key, algorithms, mut encrypted_payload, .. } => {
|
||||
// do ecdh agreement and derive master key
|
||||
let ecdh_private_key = self.ecdh_private_key.take().unwrap();
|
||||
let algorithm = self.select_algorithm(&algorithms)?;
|
||||
self.selected_algorithm = algorithm.map(|a| a.0);
|
||||
if let Some((algorithm, _speed)) = algorithm {
|
||||
let master_key = self.derive_master_key(algorithm, ecdh_private_key, &ecdh_public_key);
|
||||
self.crypto = Some(CryptoCore::new(master_key, self.salted_node_id_hash > salted_node_id_hash));
|
||||
}
|
||||
|
||||
// decrypt the payload
|
||||
let peer_payload = self
|
||||
.decrypt(&mut encrypted_payload)
|
||||
.map_err(|_| Error::CryptoInitFatal("Failed to decrypt payload"))?;
|
||||
|
||||
// create and send stage 3 reply
|
||||
self.send_message(STAGE_PENG, None, out);
|
||||
|
||||
self.next_stage = WAITING_TO_CLOSE;
|
||||
self.close_time = 60;
|
||||
Ok(InitResult::Success { peer_payload, is_initiator: true })
|
||||
}
|
||||
InitMsg::Peng { mut encrypted_payload, .. } => {
|
||||
// decrypt the payload
|
||||
let peer_payload = self
|
||||
.decrypt(&mut encrypted_payload)
|
||||
.map_err(|_| Error::CryptoInitFatal("Failed to decrypt payload"))?;
|
||||
|
||||
self.next_stage = CLOSING; // force resend when receiving any message
|
||||
Ok(InitResult::Success { peer_payload, is_initiator: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_core(&mut self) -> Option<CryptoCore> {
|
||||
self.crypto.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::NODE_ID_BYTES;
|
||||
|
||||
impl Payload for Vec<u8> {
|
||||
fn write_to(&self, buffer: &mut MsgBuffer) {
|
||||
buffer.buffer().write_all(self).expect("Buffer too small");
|
||||
buffer.set_length(self.len())
|
||||
}
|
||||
|
||||
fn read_from<R: Read>(mut r: R) -> Result<Self, Error> {
|
||||
let mut data = Vec::new();
|
||||
r.read_to_end(&mut data).map_err(|_| Error::Parse("Buffer too small"))?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pair() -> (InitState<Vec<u8>>, InitState<Vec<u8>>) {
|
||||
let rng = SystemRandom::new();
|
||||
let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
|
||||
let key_pair = Arc::new(Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap());
|
||||
let mut public_key = [0; ED25519_PUBLIC_KEY_LEN];
|
||||
public_key.clone_from_slice(key_pair.public_key().as_ref());
|
||||
let trusted_nodes = Arc::new([public_key]);
|
||||
let mut node1 = [0; NODE_ID_BYTES];
|
||||
rng.fill(&mut node1).unwrap();
|
||||
let mut node2 = [0; NODE_ID_BYTES];
|
||||
rng.fill(&mut node2).unwrap();
|
||||
let algorithms = Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
};
|
||||
let sender = InitState::new(node1, vec![1], key_pair.clone(), trusted_nodes.clone(), algorithms.clone());
|
||||
let receiver = InitState::new(node2, vec![2], key_pair, trusted_nodes, algorithms);
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_init() {
|
||||
let (mut sender, mut receiver) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
let result = receiver.handle_init(&mut out).unwrap();
|
||||
assert_eq!(receiver.stage(), STAGE_PENG);
|
||||
assert_eq!(result, InitResult::Continue);
|
||||
let result = sender.handle_init(&mut out).unwrap();
|
||||
assert_eq!(sender.stage(), WAITING_TO_CLOSE);
|
||||
let result = match result {
|
||||
InitResult::Success { .. } => receiver.handle_init(&mut out).unwrap(),
|
||||
InitResult::Continue => unreachable!(),
|
||||
};
|
||||
assert_eq!(receiver.stage(), CLOSING);
|
||||
match result {
|
||||
InitResult::Success { .. } => assert!(out.is_empty()),
|
||||
InitResult::Continue => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lost_init_sender_recovers() {
|
||||
let (mut sender, mut receiver) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
// lost ping, sender recovers
|
||||
out.clear();
|
||||
sender.every_second(&mut out).unwrap();
|
||||
let result = receiver.handle_init(&mut out).unwrap();
|
||||
assert_eq!(receiver.stage(), STAGE_PENG);
|
||||
assert_eq!(result, InitResult::Continue);
|
||||
// lost pong, sender recovers
|
||||
out.clear();
|
||||
receiver.every_second(&mut out).unwrap();
|
||||
let result = sender.handle_init(&mut out).unwrap();
|
||||
assert_eq!(sender.stage(), WAITING_TO_CLOSE);
|
||||
match result {
|
||||
InitResult::Success { .. } => {
|
||||
// lost peng, sender recovers
|
||||
out.clear();
|
||||
}
|
||||
InitResult::Continue => unreachable!(),
|
||||
};
|
||||
sender.every_second(&mut out).unwrap();
|
||||
let result = receiver.handle_init(&mut out).unwrap();
|
||||
assert_eq!(receiver.stage(), CLOSING);
|
||||
match result {
|
||||
InitResult::Success { .. } => assert!(out.is_empty()),
|
||||
InitResult::Continue => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lost_init_receiver_recovers() {
|
||||
let (mut sender, mut receiver) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
let result = receiver.handle_init(&mut out).unwrap();
|
||||
assert_eq!(receiver.stage(), STAGE_PENG);
|
||||
assert_eq!(result, InitResult::Continue);
|
||||
// lost pong, receiver recovers
|
||||
out.clear();
|
||||
sender.every_second(&mut out).unwrap();
|
||||
receiver.handle_init(&mut out).unwrap();
|
||||
let result = sender.handle_init(&mut out).unwrap();
|
||||
assert_eq!(sender.stage(), WAITING_TO_CLOSE);
|
||||
match result {
|
||||
InitResult::Success { .. } => {
|
||||
// lost peng, sender recovers
|
||||
out.clear();
|
||||
}
|
||||
InitResult::Continue => unreachable!(),
|
||||
};
|
||||
receiver.every_second(&mut out).unwrap();
|
||||
sender.handle_init(&mut out).unwrap();
|
||||
let result = receiver.handle_init(&mut out).unwrap();
|
||||
assert_eq!(receiver.stage(), CLOSING);
|
||||
match result {
|
||||
InitResult::Success { .. } => assert!(out.is_empty()),
|
||||
InitResult::Continue => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timeout() {
|
||||
let (mut sender, _receiver) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
for _ in 0..120 {
|
||||
out.clear();
|
||||
sender.every_second(&mut out).unwrap();
|
||||
}
|
||||
out.clear();
|
||||
assert!(sender.every_second(&mut out).is_err());
|
||||
assert_eq!(sender.stage(), CLOSING);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn untrusted_peer() {
|
||||
let (mut sender, _) = create_pair();
|
||||
let (_, mut receiver) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
assert!(receiver.handle_init(&mut out).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manipulated_message() {
|
||||
let (mut sender, mut receiver) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
out.message_mut()[10] ^= 0x01;
|
||||
assert!(receiver.handle_init(&mut out).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_to_self() {
|
||||
let (mut sender, _) = create_pair();
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
assert_eq!(sender.stage(), STAGE_PONG);
|
||||
assert!(sender.handle_init(&mut out).is_err());
|
||||
}
|
||||
|
||||
fn test_algorithm_negotiation(
|
||||
algos1: Algorithms, algos2: Algorithms, success: bool, selected: Option<&'static Algorithm>,
|
||||
) {
|
||||
let (mut sender, mut receiver) = create_pair();
|
||||
sender.algorithms = algos1;
|
||||
receiver.algorithms = algos2;
|
||||
let mut out = MsgBuffer::new(8);
|
||||
sender.send_ping(&mut out);
|
||||
let res = receiver.handle_init(&mut out);
|
||||
assert_eq!(res.is_ok(), success);
|
||||
if !success {
|
||||
return;
|
||||
}
|
||||
sender.handle_init(&mut out).unwrap();
|
||||
receiver.handle_init(&mut out).unwrap();
|
||||
assert_eq!(sender.selected_algorithm, selected);
|
||||
assert_eq!(sender.selected_algorithm, receiver.selected_algorithm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn algorithm_negotiation() {
|
||||
// Equal algorithms
|
||||
test_algorithm_negotiation(
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
true,
|
||||
Some(&AES_128_GCM),
|
||||
);
|
||||
|
||||
// Overlapping but different
|
||||
test_algorithm_negotiation(
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
true,
|
||||
Some(&AES_256_GCM),
|
||||
);
|
||||
|
||||
// Select fastest pair
|
||||
test_algorithm_negotiation(
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 40.0), (&AES_256_GCM, 50.0), (&CHACHA20_POLY1305, 60.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
true,
|
||||
Some(&CHACHA20_POLY1305),
|
||||
);
|
||||
|
||||
// Select unencrypted if supported by both
|
||||
test_algorithm_negotiation(
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: true,
|
||||
},
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: true,
|
||||
},
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
// Do not select unencrypted if only supported by one
|
||||
test_algorithm_negotiation(
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: true,
|
||||
},
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_128_GCM, 600.0), (&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
true,
|
||||
Some(&AES_128_GCM),
|
||||
);
|
||||
|
||||
// Fail if no match
|
||||
test_algorithm_negotiation(
|
||||
Algorithms { algorithm_speeds: smallvec![(&AES_128_GCM, 600.0)], allow_unencrypted: true },
|
||||
Algorithms {
|
||||
algorithm_speeds: smallvec![(&AES_256_GCM, 500.0), (&CHACHA20_POLY1305, 400.0)],
|
||||
allow_unencrypted: false,
|
||||
},
|
||||
false,
|
||||
Some(&AES_128_GCM),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
mod common;
|
||||
mod core;
|
||||
mod init;
|
||||
mod rotate;
|
||||
|
||||
pub use self::core::{EXTRA_LEN, TAG_LEN};
|
||||
pub use common::*;
|
|
@ -1,412 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
// This module implements a turn based key rotation.
|
||||
//
|
||||
// The main idea is that both peers periodically create ecdh key pairs and exchange their public keys to create
|
||||
// common key material. There are always two separate ecdh handshakes going on: one initiated by each peer.
|
||||
// However, one handshake is always one step ahead of the other. That means that every message being sent contains a
|
||||
// public key from step 1 of the handshake "proposed key" and a public key from step 2 of the handshake "confirmed
|
||||
// key" (all messages except first message).
|
||||
//
|
||||
// When receiving a message from the peer, the node will create a new ecdh key pair and perform the key
|
||||
// calculation for the proposed key. The peer will store the public key for the confirmation as pending to be
|
||||
// confirmed in the next cycle. Also, if the message contains a confirmation (all but the very first message do),
|
||||
// the node will use the stored private key to perform the ecdh key calculation and emit that key to be used in
|
||||
// the crypto stream.
|
||||
//
|
||||
// Upon each cycle, a node first checks if it still has a proposed key that has not been confirmed by the remote
|
||||
// peer. If so, a message must have been lost and the whole last message including the proposed key as well as the
|
||||
// last confirmed key is being resent. If no proposed key is stored, the node will create a new ecdh key pair, and
|
||||
// store the private key as proposed key. It then sends out a message containing the public key as proposal, as
|
||||
// well as confirming the pending key. This key is also emitted to be added to the crypto stream but not to be
|
||||
// used for encrypting.
|
||||
//
|
||||
// Monotonically increasing message ids guard the communication from message duplication and also serve as
|
||||
// identifiers for the keys to be used in the crypto stream. Since the keys are rotating, the last 2 bits of the
|
||||
// id are enough to identify the key.
|
||||
//
|
||||
// The whole communication is sent via the crypto stream and is therefore encrypted and protected against tampering.
|
||||
|
||||
use super::Key;
|
||||
use crate::{error::Error, util::MsgBuffer};
|
||||
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
|
||||
use ring::{
|
||||
agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519},
|
||||
rand::SystemRandom,
|
||||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::io::{self, Cursor, Read, Write};
|
||||
|
||||
type EcdhPublicKey = UnparsedPublicKey<SmallVec<[u8; 96]>>;
|
||||
type EcdhPrivateKey = EphemeralPrivateKey;
|
||||
|
||||
pub struct RotationMessage {
|
||||
message_id: u64,
|
||||
propose: EcdhPublicKey,
|
||||
confirm: Option<EcdhPublicKey>,
|
||||
}
|
||||
|
||||
impl RotationMessage {
|
||||
#[allow(dead_code)]
|
||||
pub fn read_from<R: Read>(mut r: R) -> Result<Self, io::Error> {
|
||||
let message_id = r.read_u64::<NetworkEndian>()?;
|
||||
let key_len = r.read_u8()? as usize;
|
||||
let mut key_data = smallvec![0; key_len];
|
||||
r.read_exact(&mut key_data)?;
|
||||
let propose = EcdhPublicKey::new(&X25519, key_data);
|
||||
let key_len = r.read_u8()? as usize;
|
||||
let confirm = if key_len > 0 {
|
||||
let mut key_data = smallvec![0; key_len];
|
||||
r.read_exact(&mut key_data)?;
|
||||
Some(EcdhPublicKey::new(&X25519, key_data))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(RotationMessage { message_id, propose, confirm })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn write_to<W: Write>(&self, mut w: W) -> Result<(), io::Error> {
|
||||
w.write_u64::<NetworkEndian>(self.message_id)?;
|
||||
let key_bytes = self.propose.bytes();
|
||||
w.write_u8(key_bytes.len() as u8)?;
|
||||
w.write_all(key_bytes)?;
|
||||
if let Some(ref key) = self.confirm {
|
||||
let key_bytes = key.bytes();
|
||||
w.write_u8(key_bytes.len() as u8)?;
|
||||
w.write_all(key_bytes)?;
|
||||
} else {
|
||||
w.write_u8(0)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RotationState {
|
||||
confirmed: Option<(EcdhPublicKey, u64)>, // sent by remote, already confirmed
|
||||
pending: Option<(Key, EcdhPublicKey)>, // sent by remote, to be confirmed
|
||||
proposed: Option<EcdhPrivateKey>, // my own, proposed but not confirmed
|
||||
message_id: u64,
|
||||
timeout: bool,
|
||||
}
|
||||
|
||||
pub struct RotatedKey {
|
||||
pub key: Key,
|
||||
pub id: u64,
|
||||
pub use_for_sending: bool,
|
||||
}
|
||||
|
||||
impl RotationState {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(initiator: bool, out: &mut MsgBuffer) -> Self {
|
||||
if initiator {
|
||||
let (private_key, public_key) = Self::create_key();
|
||||
Self::send(&RotationMessage { message_id: 1, confirm: None, propose: public_key }, out);
|
||||
Self { confirmed: None, pending: None, proposed: Some(private_key), message_id: 1, timeout: false }
|
||||
} else {
|
||||
Self { confirmed: None, pending: None, proposed: None, message_id: 0, timeout: false }
|
||||
}
|
||||
}
|
||||
|
||||
fn send(msg: &RotationMessage, out: &mut MsgBuffer) {
|
||||
assert!(out.is_empty());
|
||||
debug!("Rotation sending message with id {}", msg.message_id);
|
||||
let len;
|
||||
{
|
||||
let mut cursor = Cursor::new(out.buffer());
|
||||
msg.write_to(&mut cursor).expect("Buffer too small");
|
||||
len = cursor.position() as usize;
|
||||
}
|
||||
out.set_length(len);
|
||||
}
|
||||
|
||||
fn create_key() -> (EcdhPrivateKey, EcdhPublicKey) {
|
||||
let rand = SystemRandom::new();
|
||||
let private_key = EcdhPrivateKey::generate(&X25519, &rand).unwrap();
|
||||
let public_key = Self::compute_public_key(&private_key);
|
||||
(private_key, public_key)
|
||||
}
|
||||
|
||||
fn compute_public_key(private_key: &EcdhPrivateKey) -> EcdhPublicKey {
|
||||
let public_key = private_key.compute_public_key().unwrap();
|
||||
let mut vec = SmallVec::<[u8; 96]>::new();
|
||||
vec.extend_from_slice(public_key.as_ref());
|
||||
EcdhPublicKey::new(&X25519, vec)
|
||||
}
|
||||
|
||||
fn derive_key(private_key: EcdhPrivateKey, public_key: EcdhPublicKey) -> Key {
|
||||
agree_ephemeral(private_key, &public_key, |k| {
|
||||
let mut vec = Key::new();
|
||||
vec.extend_from_slice(k);
|
||||
vec
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn handle_message(&mut self, msg: &[u8]) -> Result<Option<RotatedKey>, Error> {
|
||||
let msg =
|
||||
RotationMessage::read_from(Cursor::new(msg)).map_err(|_| Error::Crypto("Rotation message too short"))?;
|
||||
Ok(self.process_message(msg))
|
||||
}
|
||||
|
||||
pub fn process_message(&mut self, msg: RotationMessage) -> Option<RotatedKey> {
|
||||
if msg.message_id <= self.message_id {
|
||||
return None;
|
||||
}
|
||||
debug!("Received rotation message with id {}", msg.message_id);
|
||||
self.timeout = false;
|
||||
// Create key from proposal and store reply as pending
|
||||
let (private_key, public_key) = Self::create_key();
|
||||
let key = Self::derive_key(private_key, msg.propose);
|
||||
self.pending = Some((key, public_key));
|
||||
// If proposed key has been confirmed, derive and use key
|
||||
if let Some(peer_key) = msg.confirm {
|
||||
if let Some(private_key) = self.proposed.take() {
|
||||
let key = Self::derive_key(private_key, peer_key);
|
||||
return Some(RotatedKey { key, id: msg.message_id, use_for_sending: true });
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cycle(&mut self, out: &mut MsgBuffer) -> Option<RotatedKey> {
|
||||
if let Some(ref private_key) = self.proposed {
|
||||
// Still a proposed key that has not been confirmed, proposal must have been lost
|
||||
if self.timeout {
|
||||
let proposed_key = Self::compute_public_key(private_key);
|
||||
if let Some((ref confirmed_key, message_id)) = self.confirmed {
|
||||
// Reconfirm last confirmed key
|
||||
Self::send(
|
||||
&RotationMessage { confirm: Some(confirmed_key.clone()), propose: proposed_key, message_id },
|
||||
out,
|
||||
);
|
||||
} else {
|
||||
// First message has been lost
|
||||
Self::send(&RotationMessage { confirm: None, propose: proposed_key, message_id: 1 }, out);
|
||||
}
|
||||
} else {
|
||||
self.timeout = true;
|
||||
}
|
||||
} else {
|
||||
// No proposed key, our turn to propose a new one
|
||||
if let Some((key, confirm_key)) = self.pending.take() {
|
||||
// Send out pending confirmation and register key for receiving
|
||||
self.message_id += 2;
|
||||
let message_id = self.message_id;
|
||||
let (private_key, propose_key) = Self::create_key();
|
||||
self.proposed = Some(private_key);
|
||||
self.confirmed = Some((confirm_key.clone(), message_id));
|
||||
Self::send(&RotationMessage { confirm: Some(confirm_key), propose: propose_key, message_id }, out);
|
||||
return Some(RotatedKey { key, id: message_id, use_for_sending: false });
|
||||
} else {
|
||||
// Nothing pending nor proposed, still waiting to receive message 1
|
||||
// Do nothing, peer will retry
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
impl MsgBuffer {
|
||||
fn msg(&mut self) -> Option<RotationMessage> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let msg = RotationMessage::read_from(Cursor::new(self.message())).unwrap();
|
||||
self.set_length(0);
|
||||
Some(msg)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_decode_message() {
|
||||
let mut data = Vec::with_capacity(100);
|
||||
let (_, key) = RotationState::create_key();
|
||||
let msg = RotationMessage { message_id: 1, propose: key, confirm: None };
|
||||
msg.write_to(&mut data).unwrap();
|
||||
let msg2 = RotationMessage::read_from(Cursor::new(&data)).unwrap();
|
||||
assert_eq!(msg.message_id, msg2.message_id);
|
||||
assert_eq!(msg.propose.bytes(), msg2.propose.bytes());
|
||||
assert_eq!(msg.confirm.map(|v| v.bytes().to_vec()), msg2.confirm.map(|v| v.bytes().to_vec()));
|
||||
let mut data = Vec::with_capacity(100);
|
||||
let (_, key1) = RotationState::create_key();
|
||||
let (_, key2) = RotationState::create_key();
|
||||
let msg = RotationMessage { message_id: 2, propose: key1, confirm: Some(key2) };
|
||||
msg.write_to(&mut data).unwrap();
|
||||
let msg2 = RotationMessage::read_from(Cursor::new(&data)).unwrap();
|
||||
assert_eq!(msg.message_id, msg2.message_id);
|
||||
assert_eq!(msg.propose.bytes(), msg2.propose.bytes());
|
||||
assert_eq!(msg.confirm.map(|v| v.bytes().to_vec()), msg2.confirm.map(|v| v.bytes().to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_rotation() {
|
||||
let mut out1 = MsgBuffer::new(8);
|
||||
let mut out2 = MsgBuffer::new(8);
|
||||
|
||||
// Initialization
|
||||
let mut node1 = RotationState::new(true, &mut out1);
|
||||
let mut node2 = RotationState::new(false, &mut out2);
|
||||
assert!(!out1.is_empty());
|
||||
let msg1 = out1.msg().unwrap();
|
||||
assert_eq!(msg1.message_id, 1);
|
||||
assert!(out2.is_empty());
|
||||
// Message 1
|
||||
let key = node2.process_message(msg1);
|
||||
assert!(key.is_none());
|
||||
// Cycle 1
|
||||
let key1 = node1.cycle(&mut out1);
|
||||
let key2 = node2.cycle(&mut out2);
|
||||
assert!(key1.is_none());
|
||||
assert!(out1.is_empty());
|
||||
assert!(key2.is_some());
|
||||
let key2 = key2.unwrap();
|
||||
assert_eq!(key2.id, 2);
|
||||
assert!(!key2.use_for_sending);
|
||||
assert!(!out2.is_empty());
|
||||
let msg2 = out2.msg().unwrap();
|
||||
assert_eq!(msg2.message_id, 2);
|
||||
assert!(msg2.confirm.is_some());
|
||||
// Message 2
|
||||
let key = node1.process_message(msg2);
|
||||
assert!(key.is_some());
|
||||
let key = key.unwrap();
|
||||
assert_eq!(key.id, 2);
|
||||
assert!(key.use_for_sending);
|
||||
// Cycle 2
|
||||
let key1 = node1.cycle(&mut out1);
|
||||
let key2 = node2.cycle(&mut out2);
|
||||
assert!(key1.is_some());
|
||||
let key1 = key1.unwrap();
|
||||
assert_eq!(key1.id, 3);
|
||||
assert!(!key1.use_for_sending);
|
||||
assert!(!out1.is_empty());
|
||||
let msg1 = out1.msg().unwrap();
|
||||
assert_eq!(msg1.message_id, 3);
|
||||
assert!(msg1.confirm.is_some());
|
||||
assert!(key2.is_none());
|
||||
assert!(out2.is_empty());
|
||||
// Message 3
|
||||
let key = node2.process_message(msg1);
|
||||
assert!(key.is_some());
|
||||
let key = key.unwrap();
|
||||
assert_eq!(key.id, 3);
|
||||
assert!(key.use_for_sending);
|
||||
// Cycle 3
|
||||
let key1 = node1.cycle(&mut out1);
|
||||
let key2 = node2.cycle(&mut out2);
|
||||
assert!(key1.is_none());
|
||||
assert!(out1.is_empty());
|
||||
assert!(key2.is_some());
|
||||
let key2 = key2.unwrap();
|
||||
assert_eq!(key2.id, 4);
|
||||
assert!(!key2.use_for_sending);
|
||||
assert!(!out2.is_empty());
|
||||
let msg2 = out2.msg().unwrap();
|
||||
assert_eq!(msg2.message_id, 4);
|
||||
assert!(msg2.confirm.is_some());
|
||||
// Message 4
|
||||
let key = node1.process_message(msg2);
|
||||
assert!(key.is_some());
|
||||
let key = key.unwrap();
|
||||
assert_eq!(key.id, 4);
|
||||
assert!(key.use_for_sending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplication() {
|
||||
let mut out1 = MsgBuffer::new(8);
|
||||
let mut out2 = MsgBuffer::new(8);
|
||||
|
||||
let mut node1 = RotationState::new(true, &mut out1);
|
||||
let mut node2 = RotationState::new(false, &mut out2);
|
||||
let msg1 = out1.clone().msg().unwrap();
|
||||
let msg1_copy = out1.msg().unwrap();
|
||||
node2.process_message(msg1);
|
||||
assert!(node2.process_message(msg1_copy).is_none());
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg2 = out2.clone().msg().unwrap();
|
||||
let msg2_copy = out2.msg().unwrap();
|
||||
// Message 2
|
||||
assert!(node1.process_message(msg2).is_some());
|
||||
assert!(node1.process_message(msg2_copy).is_none());
|
||||
// Cycle 2
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg1 = out1.clone().msg().unwrap();
|
||||
let msg1_copy = out1.msg().unwrap();
|
||||
// Message 3
|
||||
assert!(node2.process_message(msg1).is_some());
|
||||
assert!(node2.process_message(msg1_copy).is_none());
|
||||
// Cycle 3
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg2 = out2.clone().msg().unwrap();
|
||||
let msg2_copy = out2.msg().unwrap();
|
||||
// Message 4
|
||||
assert!(node1.process_message(msg2).is_some());
|
||||
assert!(node1.process_message(msg2_copy).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lost_message() {
|
||||
let mut out1 = MsgBuffer::new(8);
|
||||
let mut out2 = MsgBuffer::new(8);
|
||||
|
||||
let mut node1 = RotationState::new(true, &mut out1);
|
||||
let mut node2 = RotationState::new(false, &mut out2);
|
||||
let _msg1 = out1.msg().unwrap();
|
||||
// drop msg1
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
assert!(out2.msg().is_none());
|
||||
// Cycle 2
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg1 = out1.msg().unwrap();
|
||||
// Message 3
|
||||
assert!(node2.process_message(msg1).is_none());
|
||||
// Cycle 3
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg2 = out2.msg().unwrap();
|
||||
// Message 4
|
||||
assert!(node1.process_message(msg2).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reflect_back() {
|
||||
let mut out1 = MsgBuffer::new(8);
|
||||
let mut out2 = MsgBuffer::new(8);
|
||||
|
||||
let mut node1 = RotationState::new(true, &mut out1);
|
||||
let mut node2 = RotationState::new(false, &mut out2);
|
||||
let msg1 = out1.msg().unwrap();
|
||||
assert!(node1.process_message(msg1).is_none());
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
assert!(out2.msg().is_none());
|
||||
// Cycle 2
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg1 = out1.msg().unwrap();
|
||||
// Message 3
|
||||
assert!(node2.process_message(msg1).is_none());
|
||||
// Cycle 3
|
||||
node1.cycle(&mut out1);
|
||||
node2.cycle(&mut out2);
|
||||
let msg2 = out2.msg().unwrap();
|
||||
// Message 4
|
||||
assert!(node1.process_message(msg2).is_some());
|
||||
}
|
||||
}
|
523
src/device.rs
523
src/device.rs
|
@ -1,508 +1,65 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::io::{Result as IoResult, Error as IoError, Read, Write};
|
||||
use std::fs;
|
||||
|
||||
use std::{
|
||||
cmp,
|
||||
collections::VecDeque,
|
||||
convert::TryInto,
|
||||
fmt,
|
||||
fs::{self, File},
|
||||
io::{self, BufRead, BufReader, Cursor, Error as IoError, Read, Write},
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
os::{unix::io::AsRawFd, fd::RawFd},
|
||||
str,
|
||||
str::FromStr
|
||||
};
|
||||
use super::types::{Error, Type};
|
||||
|
||||
use crate::{crypto, error::Error, util::MsgBuffer};
|
||||
|
||||
static TUNSETIFF: libc::c_ulong = 1074025674;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct IfReqDataAddr {
|
||||
af: libc::c_int,
|
||||
addr: Ipv4Addr
|
||||
extern {
|
||||
fn setup_tap_device(fd: i32, ifname: *mut u8) -> i32;
|
||||
fn setup_tun_device(fd: i32, ifname: *mut u8) -> i32;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
union IfReqData {
|
||||
flags: libc::c_short,
|
||||
value: libc::c_int,
|
||||
addr: IfReqDataAddr,
|
||||
_dummy: [u8; 24],
|
||||
|
||||
pub struct Device {
|
||||
fd: fs::File,
|
||||
ifname: String
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct IfReq {
|
||||
ifr_name: [u8; libc::IF_NAMESIZE],
|
||||
data: IfReqData,
|
||||
}
|
||||
|
||||
impl IfReq {
|
||||
fn new(name: &str) -> Self {
|
||||
assert!(name.len() < libc::IF_NAMESIZE);
|
||||
let mut ifr_name = [0; libc::IF_NAMESIZE];
|
||||
ifr_name[..name.len()].clone_from_slice(name.as_bytes());
|
||||
Self { ifr_name, data: IfReqData { _dummy: [0; 24] } }
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of a tun/tap device
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Type {
|
||||
/// Tun interface: This interface transports IP packets.
|
||||
#[serde(rename = "tun")]
|
||||
Tun,
|
||||
/// Tap interface: This interface transports Ethernet frames.
|
||||
#[serde(rename = "tap")]
|
||||
Tap,
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Type::Tun => write!(formatter, "tun"),
|
||||
Type::Tap => write!(formatter, "tap"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Type {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match &text.to_lowercase() as &str {
|
||||
"tun" => Self::Tun,
|
||||
"tap" => Self::Tap,
|
||||
_ => return Err("Unknown device type"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Device: AsRawFd {
|
||||
/// Returns the type of this device
|
||||
fn get_type(&self) -> Type;
|
||||
|
||||
/// Returns the interface name of this device.
|
||||
fn ifname(&self) -> &str;
|
||||
|
||||
/// Reads a packet/frame from the device
|
||||
///
|
||||
/// This method reads one packet or frame (depending on the device type) into the `buffer`.
|
||||
/// The `buffer` must be large enough to hold a packet/frame of maximum size, otherwise the
|
||||
/// packet/frame will be split.
|
||||
/// The method will block until a packet/frame is ready to be read.
|
||||
/// On success, the method will return the starting position and the amount of bytes read into
|
||||
/// the buffer.
|
||||
///
|
||||
/// # Errors
|
||||
/// This method will return an error if the underlying read call fails.
|
||||
fn read(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error>;
|
||||
|
||||
/// Writes a packet/frame to the device
|
||||
///
|
||||
/// This method writes one packet or frame (depending on the device type) from `data` to the
|
||||
/// device. The data starts at the position `start` in the buffer. The buffer should have at
|
||||
/// least 4 bytes of space before the start of the packet.
|
||||
/// The method will block until the packet/frame has been written.
|
||||
///
|
||||
/// # Errors
|
||||
/// This method will return an error if the underlying read call fails.
|
||||
fn write(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error>;
|
||||
|
||||
fn get_ip(&self) -> Result<Ipv4Addr, Error>;
|
||||
}
|
||||
|
||||
/// Represents a tun/tap device
|
||||
pub struct TunTapDevice {
|
||||
fd: File,
|
||||
ifname: String,
|
||||
type_: Type,
|
||||
}
|
||||
|
||||
impl TunTapDevice {
|
||||
/// Creates a new tun/tap device
|
||||
///
|
||||
/// This method creates a new device of the `type_` kind with the name `ifname`.
|
||||
///
|
||||
/// The `ifname` must be an interface name not longer than 31 bytes. It can contain the string
|
||||
/// `%d` which will be replaced with the next free index number that guarantees that the
|
||||
/// interface name will be free. In this case, the `ifname()` method can be used to obtain the
|
||||
/// final interface name.
|
||||
///
|
||||
/// # Errors
|
||||
/// This method will return an error when the underlying system call fails. Common cases are:
|
||||
/// - The special device file `/dev/net/tun` does not exist or is not accessible by the current user.
|
||||
/// - The interface name is invalid or already in use.
|
||||
/// - The current user does not have enough permissions to create tun/tap devices (this requires root permissions).
|
||||
///
|
||||
/// # Panics
|
||||
/// This method panics if the interface name is longer than 31 bytes.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
pub fn new(ifname: &str, type_: Type, path: Option<&str>) -> io::Result<Self> {
|
||||
let path = path.unwrap_or_else(|| Self::default_path(type_));
|
||||
let fd = fs::OpenOptions::new().read(true).write(true).open(path)?;
|
||||
let flags = match type_ {
|
||||
Type::Tun => libc::IFF_TUN | libc::IFF_NO_PI,
|
||||
Type::Tap => libc::IFF_TAP | libc::IFF_NO_PI,
|
||||
impl Device {
|
||||
pub fn new(ifname: &str, type_: Type) -> IoResult<Self> {
|
||||
let fd = try!(fs::OpenOptions::new().read(true).write(true).open("/dev/net/tun"));
|
||||
let mut ifname_string = String::with_capacity(32);
|
||||
ifname_string.push_str(ifname);
|
||||
ifname_string.push('\0');
|
||||
assert!(ifname_string.len() <= 32);
|
||||
let mut ifname_c = ifname_string.into_bytes();
|
||||
let res = match type_ {
|
||||
Type::Tun => unsafe { setup_tun_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) },
|
||||
Type::Tap => unsafe { setup_tap_device(fd.as_raw_fd(), ifname_c.as_mut_ptr()) }
|
||||
};
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
ifreq.data.flags = flags as libc::c_short;
|
||||
let res = unsafe { libc::ioctl(fd.as_raw_fd(), TUNSETIFF.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => {
|
||||
let mut ifname = String::with_capacity(32);
|
||||
let mut cursor = Cursor::new(ifreq.ifr_name);
|
||||
cursor.read_to_string(&mut ifname)?;
|
||||
ifname = ifname.trim_end_matches('\0').to_owned();
|
||||
Ok(Self { fd, ifname, type_ })
|
||||
}
|
||||
_ => Err(IoError::last_os_error()),
|
||||
while ifname_c.last() == Some(&0) {
|
||||
ifname_c.pop();
|
||||
}
|
||||
Ok(Device{fd: fd, ifname: String::from_utf8(ifname_c).unwrap()})
|
||||
},
|
||||
_ => Err(IoError::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default device path for a given type
|
||||
#[inline]
|
||||
pub fn default_path(type_: Type) -> &'static str {
|
||||
match type_ {
|
||||
Type::Tun | Type::Tap => "/dev/net/tun",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[inline]
|
||||
fn correct_data_after_read(&mut self, _buffer: &mut MsgBuffer) {}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "bitrig",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[inline]
|
||||
fn correct_data_after_read(&mut self, buffer: &mut MsgBuffer) {
|
||||
if self.type_ == Type::Tun {
|
||||
// BSD-based systems add a 4-byte header containing the Ethertype for TUN
|
||||
buffer.set_start(buffer.get_start() + 4);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[inline]
|
||||
fn correct_data_before_write(&mut self, _buffer: &mut MsgBuffer) {}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "bitrig",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[inline]
|
||||
fn correct_data_before_write(&mut self, buffer: &mut MsgBuffer) {
|
||||
if self.type_ == Type::Tun {
|
||||
// BSD-based systems add a 4-byte header containing the Ethertype for TUN
|
||||
buffer.set_start(buffer.get_start() - 4);
|
||||
match buffer.message()[4] >> 4 {
|
||||
// IP version
|
||||
4 => buffer.message_mut()[0..4].copy_from_slice(&[0x00, 0x00, 0x08, 0x00]),
|
||||
6 => buffer.message_mut()[0..4].copy_from_slice(&[0x00, 0x00, 0x86, 0xdd]),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_overhead(&self) -> usize {
|
||||
40 /* for outer IPv6 header, can't be sure to only have IPv4 peers */
|
||||
+ 8 /* for outer UDP header */
|
||||
+ crypto::EXTRA_LEN + crypto::TAG_LEN /* crypto overhead */
|
||||
+ 1 /* message type header */
|
||||
+ match self.type_ {
|
||||
Type::Tap => 14, /* inner ethernet header */
|
||||
Type::Tun => 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mtu(&self, value: Option<usize>) -> io::Result<()> {
|
||||
let value = match value {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
let default_device = get_default_device()?;
|
||||
get_device_mtu(&default_device)? - self.get_overhead()
|
||||
}
|
||||
};
|
||||
info!("Setting MTU {} on device {}", value, self.ifname);
|
||||
set_device_mtu(&self.ifname, value)
|
||||
}
|
||||
|
||||
pub fn configure(&self, addr: Ipv4Addr, netmask: Ipv4Addr) -> io::Result<()> {
|
||||
set_device_addr(&self.ifname, addr)?;
|
||||
set_device_netmask(&self.ifname, netmask)?;
|
||||
set_device_enabled(&self.ifname, true)
|
||||
}
|
||||
|
||||
pub fn get_rp_filter(&self) -> io::Result<u8> {
|
||||
Ok(cmp::max(get_rp_filter("all")?, get_rp_filter(&self.ifname)?))
|
||||
}
|
||||
|
||||
pub fn fix_rp_filter(&self) -> io::Result<()> {
|
||||
if get_rp_filter("all")? > 1 {
|
||||
info!("Setting net.ipv4.conf.all.rp_filter=1");
|
||||
set_rp_filter("all", 1)?
|
||||
}
|
||||
if get_rp_filter(&self.ifname)? != 1 {
|
||||
info!("Setting net.ipv4.conf.{}.rp_filter=1", self.ifname);
|
||||
set_rp_filter(&self.ifname, 1)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for TunTapDevice {
|
||||
fn get_type(&self) -> Type {
|
||||
self.type_
|
||||
}
|
||||
|
||||
fn ifname(&self) -> &str {
|
||||
#[inline(always)]
|
||||
pub fn ifname(&self) -> &str {
|
||||
&self.ifname
|
||||
}
|
||||
|
||||
fn read(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
buffer.clear();
|
||||
let read = self.fd.read(buffer.buffer()).map_err(|e| Error::DeviceIo("Read error", e))?;
|
||||
buffer.set_length(read);
|
||||
self.correct_data_after_read(buffer);
|
||||
Ok(())
|
||||
#[inline]
|
||||
pub fn read(&mut self, mut buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
self.fd.read(&mut buffer).map_err(|_| Error::TunTapDevError("Read error"))
|
||||
}
|
||||
|
||||
fn write(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
self.correct_data_before_write(buffer);
|
||||
match self.fd.write_all(buffer.message()) {
|
||||
Ok(_) => self.fd.flush().map_err(|e| Error::DeviceIo("Flush error", e)),
|
||||
Err(e) => Err(Error::DeviceIo("Write error", e)),
|
||||
#[inline]
|
||||
pub fn write(&mut self, data: &[u8]) -> Result<(), Error> {
|
||||
match self.fd.write_all(&data) {
|
||||
Ok(_) => self.fd.flush().map_err(|_| Error::TunTapDevError("Flush error")),
|
||||
Err(_) => Err(Error::TunTapDevError("Write error"))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ip(&self) -> Result<Ipv4Addr, Error> {
|
||||
get_device_addr(&self.ifname).map_err(|e| Error::DeviceIo("Error getting IP address", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for TunTapDevice {
|
||||
#[inline]
|
||||
impl AsRawFd for Device {
|
||||
#[inline(always)]
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockDevice {
|
||||
inbound: VecDeque<Vec<u8>>,
|
||||
outbound: VecDeque<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl MockDevice {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn put_inbound(&mut self, data: Vec<u8>) {
|
||||
self.inbound.push_back(data)
|
||||
}
|
||||
|
||||
pub fn pop_outbound(&mut self) -> Option<Vec<u8>> {
|
||||
self.outbound.pop_front()
|
||||
}
|
||||
|
||||
pub fn has_inbound(&self) -> bool {
|
||||
!self.inbound.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for MockDevice {
|
||||
fn get_type(&self) -> Type {
|
||||
Type::Tun
|
||||
}
|
||||
|
||||
fn ifname(&self) -> &str {
|
||||
"mock0"
|
||||
}
|
||||
|
||||
fn read(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
if let Some(data) = self.inbound.pop_front() {
|
||||
buffer.clear();
|
||||
buffer.set_length(data.len());
|
||||
buffer.message_mut().copy_from_slice(&data);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Device("empty"))
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
|
||||
self.outbound.push_back(buffer.message().into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_ip(&self) -> Result<Ipv4Addr, Error> {
|
||||
Err(Error::Device("Dummy devices have no IP address"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MockDevice {
|
||||
fn default() -> Self {
|
||||
Self { outbound: VecDeque::with_capacity(10), inbound: VecDeque::with_capacity(10) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for MockDevice {
|
||||
#[inline]
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn set_device_mtu(ifname: &str, mtu: usize) -> io::Result<()> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
ifreq.data.value = mtu as libc::c_int;
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFMTU.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => Ok(()),
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn get_device_mtu(ifname: &str) -> io::Result<usize> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFMTU.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => Ok(unsafe { ifreq.data.value as usize }),
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn get_device_addr(ifname: &str) -> io::Result<Ipv4Addr> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFADDR.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => {
|
||||
let af = unsafe { ifreq.data.addr.af };
|
||||
if af as libc::c_int != libc::AF_INET {
|
||||
return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid address family".to_owned()));
|
||||
}
|
||||
let ip = unsafe { ifreq.data.addr.addr };
|
||||
Ok(ip)
|
||||
}
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn set_device_addr(ifname: &str, addr: Ipv4Addr) -> io::Result<()> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
ifreq.data.addr.af = libc::AF_INET as libc::c_int;
|
||||
ifreq.data.addr.addr = addr;
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFADDR.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => Ok(()),
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn get_device_netmask(ifname: &str) -> io::Result<Ipv4Addr> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFNETMASK.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => {
|
||||
let af = unsafe { ifreq.data.addr.af };
|
||||
if af as libc::c_int != libc::AF_INET {
|
||||
return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Invalid address family".to_owned()));
|
||||
}
|
||||
let ip = unsafe { ifreq.data.addr.addr };
|
||||
Ok(ip)
|
||||
}
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn set_device_netmask(ifname: &str, addr: Ipv4Addr) -> io::Result<()> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
ifreq.data.addr.af = libc::AF_INET as libc::c_int;
|
||||
ifreq.data.addr.addr = addr;
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFNETMASK.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => Ok(()),
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn set_device_enabled(ifname: &str, up: bool) -> io::Result<()> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0")?;
|
||||
let mut ifreq = IfReq::new(ifname);
|
||||
if unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFFLAGS.try_into().unwrap(), &mut ifreq) } != 0 {
|
||||
return Err(IoError::last_os_error());
|
||||
}
|
||||
if up {
|
||||
unsafe { ifreq.data.value |= libc::IFF_UP | libc::IFF_RUNNING }
|
||||
} else {
|
||||
unsafe { ifreq.data.value &= !libc::IFF_UP }
|
||||
}
|
||||
let res = unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCSIFFLAGS.try_into().unwrap(), &mut ifreq) };
|
||||
match res {
|
||||
0 => Ok(()),
|
||||
_ => Err(IoError::last_os_error()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_device() -> io::Result<String> {
|
||||
let fd = BufReader::new(File::open("/proc/net/route")?);
|
||||
let mut best = None;
|
||||
for line in fd.lines() {
|
||||
let line = line?;
|
||||
let parts = line.split('\t').collect::<Vec<_>>();
|
||||
if parts[1] == "00000000" {
|
||||
best = Some(parts[0].to_string());
|
||||
break;
|
||||
}
|
||||
if parts[2] != "00000000" {
|
||||
best = Some(parts[0].to_string())
|
||||
}
|
||||
}
|
||||
if let Some(ifname) = best {
|
||||
Ok(ifname)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "No default interface found".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rp_filter(device: &str) -> io::Result<u8> {
|
||||
let mut fd = File::open(format!("/proc/sys/net/ipv4/conf/{}/rp_filter", device))?;
|
||||
let mut contents = String::with_capacity(10);
|
||||
fd.read_to_string(&mut contents)?;
|
||||
u8::from_str(contents.trim()).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid rp_filter value"))
|
||||
}
|
||||
|
||||
fn set_rp_filter(device: &str, val: u8) -> io::Result<()> {
|
||||
let mut fd = File::create(format!("/proc/sys/net/ipv4/conf/{}/rp_filter", device))?;
|
||||
writeln!(fd, "{}", val)
|
||||
}
|
||||
|
|
55
src/error.rs
55
src/error.rs
|
@ -1,55 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use std::io;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Crypto init error, this is recoverable
|
||||
#[error("Crypto initialization error: {0}")]
|
||||
CryptoInit(&'static str),
|
||||
|
||||
/// Crypto init error, this is fatal and the init needs to be aborted
|
||||
#[error("Fatal crypto initialization error: {0}")]
|
||||
CryptoInitFatal(&'static str),
|
||||
|
||||
/// Crypto error with this one message, no permanent error
|
||||
#[error("Crypto error: {0}")]
|
||||
Crypto(&'static str),
|
||||
|
||||
#[error("Invalid crypto state: {0}")]
|
||||
InvalidCryptoState(&'static str),
|
||||
|
||||
#[error("Invalid config: {0}")]
|
||||
InvalidConfig(&'static str),
|
||||
|
||||
#[error("Socker error: {0}")]
|
||||
Socket(&'static str),
|
||||
|
||||
#[error("Socker error: {0} ({1})")]
|
||||
SocketIo(&'static str, #[source] io::Error),
|
||||
|
||||
#[error("Device error: {0}")]
|
||||
Device(&'static str),
|
||||
|
||||
#[error("Device error: {0} ({1})")]
|
||||
DeviceIo(&'static str, #[source] io::Error),
|
||||
|
||||
#[error("File error: {0}")]
|
||||
FileIo(&'static str, #[source] io::Error),
|
||||
|
||||
#[error("Message error: {0}")]
|
||||
Message(&'static str),
|
||||
|
||||
#[error("Beacon error: {0} ({1})")]
|
||||
BeaconIo(&'static str, #[source] io::Error),
|
||||
|
||||
#[error("Parse error: {0}")]
|
||||
Parse(&'static str),
|
||||
|
||||
#[error("Name can not be resolved: {0}")]
|
||||
NameUnresolvable(String),
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::types::{Error, Table, Protocol, Address};
|
||||
use super::util::{now, Time, Duration};
|
||||
|
||||
pub struct Frame;
|
||||
|
||||
impl Protocol for Frame {
|
||||
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
||||
if data.len() < 14 {
|
||||
return Err(Error::ParseError("Frame is too short"));
|
||||
}
|
||||
let mut pos = 0;
|
||||
let dst_data = &data[pos..pos+6];
|
||||
pos += 6;
|
||||
let src_data = &data[pos..pos+6];
|
||||
pos += 6;
|
||||
if data[pos] == 0x81 && data[pos+1] == 0x00 {
|
||||
pos += 2;
|
||||
if data.len() < pos + 2 {
|
||||
return Err(Error::ParseError("Vlan frame is too short"));
|
||||
}
|
||||
let mut src = [0; 16];
|
||||
let mut dst = [0; 16];
|
||||
src[0] = data[pos]; src[1] = data[pos+1];
|
||||
dst[0] = data[pos]; dst[1] = data[pos+1];
|
||||
for i in 0..6 {
|
||||
src[i+2] = src_data[i];
|
||||
dst[i+2] = dst_data[i];
|
||||
}
|
||||
Ok((Address{data: src, len: 8}, Address{data: dst, len: 8}))
|
||||
} else {
|
||||
let src = try!(Address::read_from_fixed(&src_data, 6));
|
||||
let dst = try!(Address::read_from_fixed(&dst_data, 6));
|
||||
Ok((src, dst))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SwitchTableValue {
|
||||
address: SocketAddr,
|
||||
timeout: Time
|
||||
}
|
||||
|
||||
pub struct SwitchTable {
|
||||
table: HashMap<Address, SwitchTableValue>,
|
||||
cache: Option<(Address, SocketAddr)>,
|
||||
timeout: Duration
|
||||
}
|
||||
|
||||
impl SwitchTable {
|
||||
pub fn new(timeout: Duration) -> Self {
|
||||
SwitchTable{table: HashMap::new(), cache: None, timeout: timeout}
|
||||
}
|
||||
}
|
||||
|
||||
impl Table for SwitchTable {
|
||||
fn housekeep(&mut self) {
|
||||
let now = now();
|
||||
let mut del: Vec<Address> = Vec::new();
|
||||
for (key, val) in &self.table {
|
||||
if val.timeout < now {
|
||||
del.push(key.clone());
|
||||
}
|
||||
}
|
||||
for key in del {
|
||||
info!("Forgot address {}", key);
|
||||
self.table.remove(&key);
|
||||
}
|
||||
self.cache = None;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn learn(&mut self, key: Address, _prefix_len: Option<u8>, addr: SocketAddr) {
|
||||
let value = SwitchTableValue{address: addr, timeout: now()+self.timeout as Time};
|
||||
if self.table.insert(key.clone(), value).is_none() {
|
||||
info!("Learned address {} => {}", key, addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn lookup(&mut self, key: &Address) -> Option<SocketAddr> {
|
||||
match self.table.get(key) {
|
||||
Some(value) => Some(value.address),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_all(&mut self, _addr: SocketAddr) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
use crate::{error::Error, util::run_cmd};
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
os::unix::fs::PermissionsExt,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz");
|
||||
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 EXAMPLE_CONFIG: &[u8] = include_bytes!("../assets/example.net.disabled");
|
||||
|
||||
fn systemctl_daemon_reload() {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg("daemon-reload");
|
||||
run_cmd(cmd);
|
||||
}
|
||||
|
||||
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(0o755))
|
||||
.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(0o700))
|
||||
.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.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")
|
||||
.and_then(|mut f| f.write_all(WS_PROXY_SERVICE_FILE))
|
||||
.map_err(|e| Error::FileIo("Failed to create wsporxy service file", e))?;
|
||||
systemctl_daemon_reload();
|
||||
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.target")
|
||||
.map_err(|e| Error::FileIo("Failed to remove service target 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))?;
|
||||
systemctl_daemon_reload();
|
||||
info!("Uninstall successful");
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::collections::{hash_map, HashMap};
|
||||
use std::io::Read;
|
||||
|
||||
use super::types::{Protocol, Error, Table, Address};
|
||||
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Packet;
|
||||
|
||||
impl Protocol for Packet {
|
||||
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
||||
if data.len() < 1 {
|
||||
return Err(Error::ParseError("Empty header"));
|
||||
}
|
||||
let version = data[0] >> 4;
|
||||
match version {
|
||||
4 => {
|
||||
if data.len() < 20 {
|
||||
return Err(Error::ParseError("Truncated IPv4 header"));
|
||||
}
|
||||
let src = try!(Address::read_from_fixed(&data[12..], 4));
|
||||
let dst = try!(Address::read_from_fixed(&data[16..], 4));
|
||||
Ok((src, dst))
|
||||
},
|
||||
6 => {
|
||||
if data.len() < 40 {
|
||||
return Err(Error::ParseError("Truncated IPv6 header"));
|
||||
}
|
||||
let src = try!(Address::read_from_fixed(&data[8..], 16));
|
||||
let dst = try!(Address::read_from_fixed(&data[24..], 16));
|
||||
Ok((src, dst))
|
||||
},
|
||||
_ => Err(Error::ParseError("Invalid version"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct RoutingEntry {
|
||||
address: SocketAddr,
|
||||
bytes: [u8; 16],
|
||||
prefix_len: u8
|
||||
}
|
||||
|
||||
pub struct RoutingTable(HashMap<Vec<u8>, Vec<RoutingEntry>>);
|
||||
|
||||
impl RoutingTable {
|
||||
pub fn new() -> Self {
|
||||
RoutingTable(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Table for RoutingTable {
|
||||
fn learn(&mut self, addr: Address, prefix_len: Option<u8>, address: SocketAddr) {
|
||||
let prefix_len = match prefix_len {
|
||||
Some(val) => val,
|
||||
None => addr.len * 8
|
||||
};
|
||||
info!("New routing entry: {}/{} => {}", addr, prefix_len, address);
|
||||
let group_len = prefix_len as usize / 8;
|
||||
assert!(group_len <= 16);
|
||||
let mut group_bytes = Vec::with_capacity(group_len);
|
||||
for i in 0..group_len {
|
||||
group_bytes.push(addr.data[i]);
|
||||
}
|
||||
let routing_entry = RoutingEntry{address: address, bytes: addr.data, prefix_len: prefix_len};
|
||||
match self.0.entry(group_bytes) {
|
||||
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry),
|
||||
hash_map::Entry::Vacant(entry) => { entry.insert(vec![routing_entry]); () }
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&mut self, addr: &Address) -> Option<SocketAddr> {
|
||||
let len = addr.len as usize;
|
||||
let mut found = None;
|
||||
let mut found_len: isize = -1;
|
||||
for i in 0..len+1 {
|
||||
if let Some(group) = self.0.get(&addr.data[0..len-i]) {
|
||||
for entry in group {
|
||||
let mut match_len = 0;
|
||||
for j in 0..addr.len as usize {
|
||||
let b = addr.data[j] ^ entry.bytes[j];
|
||||
if b == 0 {
|
||||
match_len += 8;
|
||||
} else {
|
||||
match_len += b.leading_zeros();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if match_len as u8 >= entry.prefix_len && match_len as isize > found_len {
|
||||
found = Some(entry.address);
|
||||
found_len = match_len as isize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
fn housekeep(&mut self) {
|
||||
//nothing to do
|
||||
}
|
||||
|
||||
fn remove_all(&mut self, _addr: SocketAddr) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
429
src/main.rs
429
src/main.rs
|
@ -1,339 +1,162 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
#[macro_use] extern crate log;
|
||||
extern crate time;
|
||||
extern crate docopt;
|
||||
extern crate rustc_serialize;
|
||||
extern crate epoll;
|
||||
extern crate signal;
|
||||
extern crate nix;
|
||||
extern crate libc;
|
||||
extern crate aligned_alloc;
|
||||
extern crate rand;
|
||||
#[cfg(feature = "bench")] extern crate test;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use] mod util;
|
||||
mod types;
|
||||
mod crypto;
|
||||
mod udpmessage;
|
||||
mod ethernet;
|
||||
mod ip;
|
||||
mod cloud;
|
||||
mod device;
|
||||
#[cfg(test)] mod tests;
|
||||
#[cfg(feature = "bench")] mod benches;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempfile;
|
||||
use docopt::Docopt;
|
||||
|
||||
#[macro_use]
|
||||
pub mod util;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod tests;
|
||||
pub mod beacon;
|
||||
pub mod cloud;
|
||||
pub mod config;
|
||||
pub mod crypto;
|
||||
pub mod device;
|
||||
pub mod error;
|
||||
#[cfg(feature = "installer")]
|
||||
pub mod installer;
|
||||
pub mod messages;
|
||||
pub mod net;
|
||||
pub mod oldconfig;
|
||||
pub mod payload;
|
||||
pub mod poll;
|
||||
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;
|
||||
use std::hash::{Hash, SipHasher, Hasher};
|
||||
use std::str::FromStr;
|
||||
use std::process::Command;
|
||||
|
||||
use structopt::StructOpt;
|
||||
use device::Device;
|
||||
use ethernet::SwitchTable;
|
||||
use ip::RoutingTable;
|
||||
use types::{Error, Mode, Type, Range, Table, Protocol};
|
||||
use cloud::GenericCloud;
|
||||
use udpmessage::VERSION;
|
||||
use crypto::{Crypto, CryptoMethod};
|
||||
use util::Duration;
|
||||
|
||||
use std::{
|
||||
fs::{self, File, Permissions},
|
||||
io::{self, Write},
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::Path,
|
||||
process,
|
||||
str::FromStr,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cloud::GenericCloud,
|
||||
config::{Args, Command, Config, DEFAULT_PORT},
|
||||
crypto::Crypto,
|
||||
device::{Device, TunTapDevice, Type},
|
||||
net::Socket,
|
||||
oldconfig::OldConfigFile,
|
||||
payload::Protocol,
|
||||
util::SystemTimeSource,
|
||||
};
|
||||
struct SimpleLogger;
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
use crate::wsproxy::ProxyConnection;
|
||||
|
||||
struct DualLogger {
|
||||
file: Option<Mutex<File>>,
|
||||
}
|
||||
|
||||
impl DualLogger {
|
||||
pub fn new<P: AsRef<Path>>(path: Option<P>) -> Result<Self, io::Error> {
|
||||
if let Some(path) = path {
|
||||
let path = path.as_ref();
|
||||
if path.exists() {
|
||||
fs::remove_file(path)?
|
||||
}
|
||||
let file = File::create(path)?;
|
||||
Ok(DualLogger { file: Some(Mutex::new(file)) })
|
||||
} else {
|
||||
Ok(DualLogger { file: None })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl log::Log for DualLogger {
|
||||
#[inline]
|
||||
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||
impl log::Log for SimpleLogger {
|
||||
#[inline(always)]
|
||||
fn enabled(&self, _metadata: &log::LogMetadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log(&self, record: &log::Record) {
|
||||
#[inline(always)]
|
||||
fn log(&self, record: &log::LogRecord) {
|
||||
if self.enabled(record.metadata()) {
|
||||
println!("{} - {}", record.level(), record.args());
|
||||
if let Some(ref file) = self.file {
|
||||
let mut file = file.lock().expect("Lock poisoned");
|
||||
let time = chrono::Local::now().format("%F %H:%M:%S");
|
||||
writeln!(file, "{} - {} - {}", time, record.level(), record.args())
|
||||
.expect("Failed to write to logfile");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&self) {
|
||||
if let Some(ref file) = self.file {
|
||||
let mut file = file.lock().expect("Lock poisoned");
|
||||
try_fail!(file.flush(), "Logging error: {}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_script(script: &str, ifname: &str) {
|
||||
let mut cmd = process::Command::new("sh");
|
||||
cmd.arg("-c").arg(script).env("IFNAME", ifname);
|
||||
static USAGE: &'static str = include_str!("usage.txt");
|
||||
|
||||
#[derive(RustcDecodable, Debug)]
|
||||
struct Args {
|
||||
flag_type: Type,
|
||||
flag_mode: Mode,
|
||||
flag_shared_key: Option<String>,
|
||||
flag_crypto: CryptoMethod,
|
||||
flag_subnet: Vec<String>,
|
||||
flag_device: String,
|
||||
flag_listen: String,
|
||||
flag_network_id: Option<String>,
|
||||
flag_connect: Vec<String>,
|
||||
flag_peer_timeout: Duration,
|
||||
flag_dst_timeout: Duration,
|
||||
flag_verbose: bool,
|
||||
flag_quiet: bool,
|
||||
flag_ifup: Option<String>,
|
||||
flag_ifdown: Option<String>,
|
||||
flag_version: bool
|
||||
}
|
||||
|
||||
fn run_script(script: String, ifname: &str) {
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c").arg(&script).env("IFNAME", ifname);
|
||||
debug!("Running script: {:?}", cmd);
|
||||
match cmd.status() {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
error!("Script returned with error: {:?}", status.code())
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Failed to execute script {:?}: {}", script, e),
|
||||
Ok(status) => match status.success() {
|
||||
true => (),
|
||||
false => error!("Script returned with error: {:?}", status.code())
|
||||
},
|
||||
Err(e) => error!("Failed to execute script {:?}: {}", script, e)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ip_netmask(addr: &str) -> Result<(Ipv4Addr, Ipv4Addr), String> {
|
||||
let (ip_str, len_str) = match addr.find('/') {
|
||||
Some(pos) => (&addr[..pos], &addr[pos + 1..]),
|
||||
None => (addr, "24"),
|
||||
};
|
||||
let prefix_len = u8::from_str(len_str).map_err(|_| format!("Invalid prefix length: {}", len_str))?;
|
||||
if prefix_len > 32 {
|
||||
return Err(format!("Invalid prefix length: {}", prefix_len));
|
||||
}
|
||||
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());
|
||||
Ok((ip, netmask))
|
||||
}
|
||||
|
||||
fn setup_device(config: &Config) -> TunTapDevice {
|
||||
let device = try_fail!(
|
||||
TunTapDevice::new(&config.device_name, config.device_type, config.device_path.as_ref().map(|s| s as &str)),
|
||||
"Failed to open virtual {} interface {}: {}",
|
||||
config.device_type,
|
||||
config.device_name
|
||||
);
|
||||
fn run<T: Protocol> (args: Args) {
|
||||
let device = try_fail!(Device::new(&args.flag_device, args.flag_type),
|
||||
"Failed to open virtual {} interface {}: {}", args.flag_type, &args.flag_device);
|
||||
info!("Opened device {}", device.ifname());
|
||||
config.call_hook("device_setup", vec![("IFNAME", device.ifname())], true);
|
||||
if let Err(err) = device.set_mtu(None) {
|
||||
error!("Error setting optimal MTU on {}: {}", device.ifname(), err);
|
||||
let mut ranges = Vec::with_capacity(args.flag_subnet.len());
|
||||
for s in args.flag_subnet {
|
||||
ranges.push(try_fail!(Range::from_str(&s), "Invalid subnet format: {} ({})", s));
|
||||
}
|
||||
if let Some(ip) = &config.ip {
|
||||
let (ip, netmask) = try_fail!(parse_ip_netmask(ip), "Invalid ip address given: {}");
|
||||
info!("Configuring device with ip {}, netmask {}", ip, netmask);
|
||||
try_fail!(device.configure(ip, netmask), "Failed to configure device: {}");
|
||||
}
|
||||
if let Some(script) = &config.ifup {
|
||||
run_script(script, device.ifname());
|
||||
}
|
||||
if config.fix_rp_filter {
|
||||
try_fail!(device.fix_rp_filter(), "Failed to change rp_filter settings: {}");
|
||||
}
|
||||
if let Ok(val) = device.get_rp_filter() {
|
||||
if val != 1 {
|
||||
warn!("Your networking configuration might be affected by a vulnerability (https://vpncloud.ddswd.de/docs/security/cve-2019-14899/), please change your rp_filter setting to 1 (currently {}).", val);
|
||||
}
|
||||
}
|
||||
config.call_hook("device_configured", vec![("IFNAME", device.ifname())], true);
|
||||
device
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn run<P: Protocol, S: Socket>(config: Config, socket: S) {
|
||||
let device = setup_device(&config);
|
||||
let port_forwarding = if config.port_forwarding { socket.create_port_forwarding() } else { None };
|
||||
let stats_file = match config.stats_file {
|
||||
None => None,
|
||||
Some(ref name) => {
|
||||
let path = Path::new(name);
|
||||
if path.exists() {
|
||||
try_fail!(fs::remove_file(path), "Failed to remove file {}: {}", name);
|
||||
}
|
||||
let file = try_fail!(File::create(name), "Failed to create stats file: {}");
|
||||
try_fail!(
|
||||
fs::set_permissions(name, Permissions::from_mode(0o644)),
|
||||
"Failed to set permissions on stats file: {}"
|
||||
);
|
||||
Some(file)
|
||||
}
|
||||
let dst_timeout = args.flag_dst_timeout;
|
||||
let peer_timeout = args.flag_peer_timeout;
|
||||
let (learning, broadcasting, table): (bool, bool, Box<Table>) = match args.flag_mode {
|
||||
Mode::Normal => match args.flag_type {
|
||||
Type::Tap => (true, true, Box::new(SwitchTable::new(dst_timeout))),
|
||||
Type::Tun => (false, false, Box::new(RoutingTable::new()))
|
||||
},
|
||||
Mode::Router => (false, false, Box::new(RoutingTable::new())),
|
||||
Mode::Switch => (true, true, Box::new(SwitchTable::new(dst_timeout))),
|
||||
Mode::Hub => (false, true, Box::new(SwitchTable::new(dst_timeout)))
|
||||
};
|
||||
let mut cloud =
|
||||
GenericCloud::<TunTapDevice, P, S, SystemTimeSource>::new(&config, socket, device, port_forwarding, stats_file);
|
||||
for mut addr in config.peers {
|
||||
if addr.find(':').unwrap_or(0) <= addr.find(']').unwrap_or(0) {
|
||||
// : not present or only in IPv6 address
|
||||
addr = format!("{}:{}", addr, DEFAULT_PORT)
|
||||
}
|
||||
try_fail!(cloud.connect(&addr as &str), "Failed to send message to {}: {}", &addr);
|
||||
cloud.add_reconnect_peer(addr);
|
||||
let network_id = args.flag_network_id.map(|name| {
|
||||
let mut s = SipHasher::new();
|
||||
name.hash(&mut s);
|
||||
s.finish()
|
||||
});
|
||||
Crypto::init();
|
||||
let crypto = match args.flag_shared_key {
|
||||
Some(key) => Crypto::from_shared_key(args.flag_crypto, &key),
|
||||
None => Crypto::None
|
||||
};
|
||||
let mut cloud = GenericCloud::<T>::new(device, args.flag_listen, network_id, table, peer_timeout, learning, broadcasting, ranges, crypto);
|
||||
if let Some(script) = args.flag_ifup {
|
||||
run_script(script, cloud.ifname());
|
||||
}
|
||||
if config.daemonize {
|
||||
info!("Running process as daemon");
|
||||
let mut daemonize = daemonize::Daemonize::new();
|
||||
if let Some(user) = config.user {
|
||||
daemonize = daemonize.user(&user as &str);
|
||||
}
|
||||
if let Some(group) = config.group {
|
||||
daemonize = daemonize.group(&group as &str);
|
||||
}
|
||||
if let Some(pid_file) = config.pid_file {
|
||||
daemonize = daemonize.pid_file(pid_file).chown_pid_file(true);
|
||||
}
|
||||
try_fail!(daemonize.start(), "Failed to daemonize: {}");
|
||||
} else if config.user.is_some() || config.group.is_some() {
|
||||
info!("Dropping privileges");
|
||||
let mut pd = privdrop::PrivDrop::default();
|
||||
if let Some(user) = config.user {
|
||||
pd = pd.user(user);
|
||||
}
|
||||
if let Some(group) = config.group {
|
||||
pd = pd.group(group);
|
||||
}
|
||||
try_fail!(pd.apply(), "Failed to drop privileges: {}");
|
||||
for addr in &args.flag_connect {
|
||||
try_fail!(cloud.connect(&addr as &str, true), "Failed to send message to {}: {}", &addr);
|
||||
}
|
||||
cloud.run();
|
||||
if let Some(script) = config.ifdown {
|
||||
run_script(&script, cloud.ifname());
|
||||
if let Some(script) = args.flag_ifdown {
|
||||
run_script(script, cloud.ifname());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Args = Args::from_args();
|
||||
if args.version {
|
||||
println!("VpnCloud v{}", env!("CARGO_PKG_VERSION"));
|
||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit());
|
||||
if args.flag_version {
|
||||
Crypto::init();
|
||||
println!("VpnCloud v{}, protocol version {}, libsodium {} (AES256: {})",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
VERSION,
|
||||
Crypto::sodium_version(),
|
||||
Crypto::aes256_available()
|
||||
);
|
||||
return;
|
||||
}
|
||||
let logger = try_fail!(DualLogger::new(args.log_file.as_ref()), "Failed to open logfile: {}");
|
||||
log::set_boxed_logger(Box::new(logger)).unwrap();
|
||||
assert!(!args.verbose || !args.quiet);
|
||||
log::set_max_level(if args.verbose {
|
||||
log::LevelFilter::Debug
|
||||
} else if args.quiet {
|
||||
log::LevelFilter::Error
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
});
|
||||
if let Some(cmd) = args.cmd {
|
||||
match cmd {
|
||||
Command::GenKey { password } => {
|
||||
let (privkey, pubkey) = Crypto::generate_keypair(password.as_deref());
|
||||
println!("Private key: {}\nPublic key: {}\n", privkey, pubkey);
|
||||
println!(
|
||||
"Attention: Keep the private key secret and use only the public key on other nodes to establish trust."
|
||||
);
|
||||
}
|
||||
Command::MigrateConfig { config_file } => {
|
||||
info!("Trying to convert from old config format");
|
||||
let f = try_fail!(File::open(&config_file), "Failed to open config file: {:?}");
|
||||
let config_file_old: OldConfigFile =
|
||||
try_fail!(serde_yaml::from_reader(f), "Config file not valid for version 1: {:?}");
|
||||
let new_config = config_file_old.convert();
|
||||
info!("Successfully converted from old format");
|
||||
info!("Renaming original file to {}.orig", config_file);
|
||||
try_fail!(
|
||||
fs::rename(&config_file, format!("{}.orig", config_file)),
|
||||
"Failed to rename original file: {:?}"
|
||||
);
|
||||
info!("Writing new config back into {}", config_file);
|
||||
let f = try_fail!(File::create(&config_file), "Failed to open config file: {:?}");
|
||||
try_fail!(
|
||||
fs::set_permissions(&config_file, fs::Permissions::from_mode(0o600)),
|
||||
"Failed to set permissions on file: {:?}"
|
||||
);
|
||||
try_fail!(serde_yaml::to_writer(f, &new_config), "Failed to write converted config: {:?}");
|
||||
}
|
||||
Command::Completion { shell } => {
|
||||
Args::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut io::stdout());
|
||||
}
|
||||
#[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: {}");
|
||||
}
|
||||
}
|
||||
log::set_logger(|max_log_level| {
|
||||
assert!(!args.flag_verbose || !args.flag_quiet);
|
||||
if args.flag_verbose {
|
||||
max_log_level.set(log::LogLevelFilter::Debug);
|
||||
} else if args.flag_quiet {
|
||||
max_log_level.set(log::LogLevelFilter::Error);
|
||||
} else {
|
||||
max_log_level.set(log::LogLevelFilter::Info);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let mut config = Config::default();
|
||||
if let Some(ref file) = args.config {
|
||||
info!("Reading config file '{}'", file);
|
||||
let f = try_fail!(File::open(file), "Failed to open config file: {:?}");
|
||||
let config_file = match serde_yaml::from_reader(f) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
error!("Failed to read config file: {}", err);
|
||||
info!("Trying to convert from old config format");
|
||||
let f = try_fail!(File::open(file), "Failed to open config file: {:?}");
|
||||
let config_file_old: OldConfigFile =
|
||||
try_fail!(serde_yaml::from_reader(f), "Config file is neither version 2 nor version 1: {:?}");
|
||||
let new_config = config_file_old.convert();
|
||||
info!("Successfully converted from old format, please migrate your config using migrate-config");
|
||||
new_config
|
||||
}
|
||||
};
|
||||
config.merge_file(config_file)
|
||||
}
|
||||
config.merge_args(args);
|
||||
debug!("Config: {:?}", config);
|
||||
if config.crypto.password.is_none() && config.crypto.private_key.is_none() {
|
||||
error!("Either password or private key must be set in config or given as parameter");
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
if config.listen.starts_with("ws://") {
|
||||
let socket = try_fail!(ProxyConnection::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
|
||||
match config.device_type {
|
||||
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||
Type::Tun => run::<payload::Packet, _>(config, socket),
|
||||
}
|
||||
return;
|
||||
}
|
||||
let socket = try_fail!(UdpSocket::listen(&config.listen), "Failed to open socket {}: {}", config.listen);
|
||||
match config.device_type {
|
||||
Type::Tap => run::<payload::Frame, _>(config, socket),
|
||||
Type::Tun => run::<payload::Packet, _>(config, socket),
|
||||
Box::new(SimpleLogger)
|
||||
}).unwrap();
|
||||
debug!("Args: {:?}", args);
|
||||
match args.flag_type {
|
||||
Type::Tap => run::<ethernet::Frame>(args),
|
||||
Type::Tun => run::<ip::Packet>(args),
|
||||
}
|
||||
}
|
||||
|
|
265
src/messages.rs
265
src/messages.rs
|
@ -1,265 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use crate::{
|
||||
crypto::Payload,
|
||||
error::Error,
|
||||
types::{NodeId, Range, RangeList, NODE_ID_BYTES},
|
||||
util::MsgBuffer,
|
||||
};
|
||||
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{
|
||||
io::{self, Cursor, Read, Seek, SeekFrom, Take, Write},
|
||||
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
};
|
||||
|
||||
pub const MESSAGE_TYPE_DATA: u8 = 0;
|
||||
pub const MESSAGE_TYPE_NODE_INFO: u8 = 1;
|
||||
pub const MESSAGE_TYPE_KEEPALIVE: u8 = 2;
|
||||
pub const MESSAGE_TYPE_CLOSE: u8 = 0xff;
|
||||
|
||||
pub type AddrList = SmallVec<[SocketAddr; 4]>;
|
||||
pub type PeerList = SmallVec<[PeerInfo; 16]>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PeerInfo {
|
||||
pub node_id: Option<NodeId>,
|
||||
pub addrs: AddrList,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct NodeInfo {
|
||||
pub node_id: NodeId,
|
||||
pub peers: PeerList,
|
||||
pub claims: RangeList,
|
||||
pub peer_timeout: Option<u16>,
|
||||
pub addrs: AddrList,
|
||||
}
|
||||
|
||||
impl NodeInfo {
|
||||
const PART_CLAIMS: u8 = 2;
|
||||
const PART_END: u8 = 0;
|
||||
const PART_NODEID: u8 = 4;
|
||||
const PART_PEERS: u8 = 1;
|
||||
const PART_PEER_TIMEOUT: u8 = 3;
|
||||
const PART_ADDRS: u8 = 5;
|
||||
|
||||
fn read_addr_list<R: Read>(r: &mut Take<R>) -> Result<AddrList, io::Error> {
|
||||
let flags = r.read_u8()?;
|
||||
Self::read_addr_list_inner(r, flags)
|
||||
}
|
||||
|
||||
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_ipv6_addrs = (flags & 0x38) as usize / 8;
|
||||
let mut addrs = SmallVec::with_capacity(num_ipv4_addrs + num_ipv6_addrs);
|
||||
for _ in 0..num_ipv6_addrs {
|
||||
let mut ip = [0u8; 16];
|
||||
r.read_exact(&mut ip)?;
|
||||
let port = r.read_u16::<NetworkEndian>()?;
|
||||
let addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0));
|
||||
addrs.push(addr);
|
||||
}
|
||||
for _ in 0..num_ipv4_addrs {
|
||||
let mut ip = [0u8; 4];
|
||||
r.read_exact(&mut ip)?;
|
||||
let port = r.read_u16::<NetworkEndian>()?;
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(ip), port));
|
||||
addrs.push(addr);
|
||||
}
|
||||
Ok(addrs)
|
||||
}
|
||||
|
||||
fn decode_peer_list_part<R: Read>(r: &mut Take<R>) -> Result<PeerList, io::Error> {
|
||||
let mut peers = smallvec![];
|
||||
while r.limit() > 0 {
|
||||
let flags = r.read_u8()?;
|
||||
let has_node_id = (flags & 0x80) != 0;
|
||||
let mut node_id = None;
|
||||
if has_node_id {
|
||||
let mut id = [0; NODE_ID_BYTES];
|
||||
r.read_exact(&mut id)?;
|
||||
node_id = Some(id)
|
||||
}
|
||||
let addrs = Self::read_addr_list_inner(r, flags)?;
|
||||
peers.push(PeerInfo { addrs, node_id })
|
||||
}
|
||||
Ok(peers)
|
||||
}
|
||||
|
||||
fn decode_claims_part<R: Read>(mut r: &mut Take<R>) -> Result<RangeList, Error> {
|
||||
let mut claims = smallvec![];
|
||||
while r.limit() > 0 {
|
||||
claims.push(Range::read_from(&mut r)?);
|
||||
}
|
||||
Ok(claims)
|
||||
}
|
||||
|
||||
fn decode_internal<R: Read>(mut r: R) -> Result<Self, Error> {
|
||||
let mut peers = smallvec![];
|
||||
let mut claims = smallvec![];
|
||||
let mut peer_timeout = None;
|
||||
let mut node_id = None;
|
||||
let mut addrs = smallvec![];
|
||||
loop {
|
||||
let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?;
|
||||
if part == Self::PART_END {
|
||||
break;
|
||||
}
|
||||
let part_len = r.read_u16::<NetworkEndian>().map_err(|_| Error::Message("Truncated message"))? as usize;
|
||||
let mut rp = r.take(part_len as u64);
|
||||
match part {
|
||||
Self::PART_PEERS => {
|
||||
peers = Self::decode_peer_list_part(&mut rp).map_err(|_| Error::Message("Truncated message"))?
|
||||
}
|
||||
Self::PART_CLAIMS => claims = Self::decode_claims_part(&mut rp)?,
|
||||
Self::PART_PEER_TIMEOUT => {
|
||||
peer_timeout =
|
||||
Some(rp.read_u16::<NetworkEndian>().map_err(|_| Error::Message("Truncated message"))?)
|
||||
}
|
||||
Self::PART_NODEID => {
|
||||
let mut data = [0; NODE_ID_BYTES];
|
||||
rp.read_exact(&mut data).map_err(|_| Error::Message("Truncated message"))?;
|
||||
node_id = Some(data);
|
||||
}
|
||||
Self::PART_ADDRS => {
|
||||
addrs = Self::read_addr_list(&mut rp).map_err(|_| Error::Message("Truncated message"))?;
|
||||
}
|
||||
_ => {
|
||||
let mut data = vec![0; part_len];
|
||||
rp.read_exact(&mut data).map_err(|_| Error::Message("Truncated message"))?;
|
||||
}
|
||||
}
|
||||
r = rp.into_inner();
|
||||
}
|
||||
let node_id = match node_id {
|
||||
Some(node_id) => node_id,
|
||||
None => return Err(Error::Message("Payload without node_id")),
|
||||
};
|
||||
Ok(Self { node_id, peers, claims, peer_timeout, addrs })
|
||||
}
|
||||
|
||||
pub fn decode<R: Read>(r: R) -> Result<Self, Error> {
|
||||
Self::decode_internal(r).map_err(|_| Error::Message("Input data too short"))
|
||||
}
|
||||
|
||||
fn encode_peer_list_part<W: Write>(&self, mut out: W) -> Result<(), io::Error> {
|
||||
for p in &self.peers {
|
||||
let mut addr_ipv4: SmallVec<[SocketAddrV4; 16]> = smallvec![];
|
||||
let mut addr_ipv6: SmallVec<[SocketAddrV6; 16]> = smallvec![];
|
||||
for a in &p.addrs {
|
||||
match a {
|
||||
SocketAddr::V4(addr) => addr_ipv4.push(*addr),
|
||||
SocketAddr::V6(addr) => addr_ipv6.push(*addr),
|
||||
}
|
||||
}
|
||||
while addr_ipv4.len() >= 8 {
|
||||
addr_ipv4.pop();
|
||||
}
|
||||
while addr_ipv6.len() >= 8 {
|
||||
addr_ipv6.pop();
|
||||
}
|
||||
let mut flags = addr_ipv6.len() as u8 * 8 + addr_ipv4.len() as u8;
|
||||
if p.node_id.is_some() {
|
||||
flags += 0x80;
|
||||
}
|
||||
out.write_u8(flags)?;
|
||||
if let Some(node_id) = &p.node_id {
|
||||
out.write_all(node_id)?;
|
||||
}
|
||||
for a in addr_ipv6 {
|
||||
out.write_all(&a.ip().octets())?;
|
||||
out.write_u16::<NetworkEndian>(a.port())?;
|
||||
}
|
||||
for a in addr_ipv4 {
|
||||
out.write_all(&a.ip().octets())?;
|
||||
out.write_u16::<NetworkEndian>(a.port())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_addrs_part<W: Write>(&self, mut out: W) -> Result<(), io::Error> {
|
||||
let mut addr_ipv4: SmallVec<[SocketAddrV4; 16]> = smallvec![];
|
||||
let mut addr_ipv6: SmallVec<[SocketAddrV6; 16]> = smallvec![];
|
||||
for a in &self.addrs {
|
||||
match a {
|
||||
SocketAddr::V4(addr) => addr_ipv4.push(*addr),
|
||||
SocketAddr::V6(addr) => addr_ipv6.push(*addr),
|
||||
}
|
||||
}
|
||||
while addr_ipv4.len() >= 8 {
|
||||
addr_ipv4.pop();
|
||||
}
|
||||
while addr_ipv6.len() >= 8 {
|
||||
addr_ipv6.pop();
|
||||
}
|
||||
let flags = addr_ipv6.len() as u8 * 8 + addr_ipv4.len() as u8;
|
||||
out.write_u8(flags)?;
|
||||
for a in addr_ipv6 {
|
||||
out.write_all(&a.ip().octets())?;
|
||||
out.write_u16::<NetworkEndian>(a.port())?;
|
||||
}
|
||||
for a in addr_ipv4 {
|
||||
out.write_all(&a.ip().octets())?;
|
||||
out.write_u16::<NetworkEndian>(a.port())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_part<F: FnOnce(&mut Cursor<&mut [u8]>) -> Result<(), io::Error>>(
|
||||
cursor: &mut Cursor<&mut [u8]>, part: u8, f: F,
|
||||
) -> Result<(), io::Error> {
|
||||
cursor.write_u8(part)?;
|
||||
cursor.write_u16::<NetworkEndian>(0)?;
|
||||
let part_start = cursor.position();
|
||||
f(cursor)?;
|
||||
let part_end = cursor.position();
|
||||
let len = part_end - part_start;
|
||||
cursor.seek(SeekFrom::Start(part_start - 2))?;
|
||||
cursor.write_u16::<NetworkEndian>(len as u16)?;
|
||||
cursor.seek(SeekFrom::Start(part_end))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_internal(&self, buffer: &mut MsgBuffer) -> Result<(), io::Error> {
|
||||
let len;
|
||||
{
|
||||
let mut cursor = Cursor::new(buffer.buffer());
|
||||
Self::encode_part(&mut cursor, Self::PART_NODEID, |cursor| cursor.write_all(&self.node_id))?;
|
||||
Self::encode_part(&mut cursor, Self::PART_PEERS, |cursor| self.encode_peer_list_part(cursor))?;
|
||||
Self::encode_part(&mut cursor, Self::PART_CLAIMS, |mut cursor| {
|
||||
for c in &self.claims {
|
||||
c.write_to(&mut cursor);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
if let Some(timeout) = self.peer_timeout {
|
||||
Self::encode_part(&mut cursor, Self::PART_PEER_TIMEOUT, |cursor| {
|
||||
cursor.write_u16::<NetworkEndian>(timeout)
|
||||
})?
|
||||
}
|
||||
Self::encode_part(&mut cursor, Self::PART_ADDRS, |cursor| self.encode_addrs_part(cursor))?;
|
||||
cursor.write_u8(Self::PART_END)?;
|
||||
len = cursor.position() as usize;
|
||||
}
|
||||
buffer.set_length(len);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encode(&self, buffer: &mut MsgBuffer) {
|
||||
self.encode_internal(buffer).expect("Buffer too small")
|
||||
}
|
||||
}
|
||||
|
||||
impl Payload for NodeInfo {
|
||||
fn write_to(&self, buffer: &mut MsgBuffer) {
|
||||
self.encode(buffer)
|
||||
}
|
||||
|
||||
fn read_from<R: Read>(r: R) -> Result<Self, Error> {
|
||||
Self::decode(r)
|
||||
}
|
||||
}
|
183
src/net.rs
183
src/net.rs
|
@ -1,183 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
io::{self, ErrorKind},
|
||||
net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket},
|
||||
os::unix::io::{AsRawFd, RawFd},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource};
|
||||
use crate::{config::DEFAULT_PORT, port_forwarding::PortForwarding};
|
||||
|
||||
pub fn mapped_addr(addr: SocketAddr) -> SocketAddr {
|
||||
// HOT PATH
|
||||
match addr {
|
||||
SocketAddr::V4(addr4) => SocketAddr::new(IpAddr::V6(addr4.ip().to_ipv6_mapped()), addr4.port()),
|
||||
_ => addr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ip() -> IpAddr {
|
||||
let s = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
s.connect("8.8.8.8:0").unwrap();
|
||||
s.local_addr().unwrap().ip()
|
||||
}
|
||||
|
||||
pub trait Socket: AsRawFd + Sized {
|
||||
fn listen(addr: &str) -> Result<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>;
|
||||
fn address(&self) -> Result<SocketAddr, io::Error>;
|
||||
fn create_port_forwarding(&self) -> Option<PortForwarding>;
|
||||
}
|
||||
|
||||
pub fn parse_listen(addr: &str, default_port: u16) -> SocketAddr {
|
||||
if let Some(addr) = addr.strip_prefix("*:") {
|
||||
let port = try_fail!(addr.parse::<u16>(), "Invalid port: {}");
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
||||
} else if addr.contains(':') {
|
||||
try_fail!(addr.parse::<SocketAddr>(), "Invalid address: {}: {}", addr)
|
||||
} else if let Ok(port) = addr.parse::<u16>() {
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)
|
||||
} else {
|
||||
let ip = try_fail!(addr.parse::<IpAddr>(), "Invalid addr: {}");
|
||||
SocketAddr::new(ip, default_port)
|
||||
}
|
||||
}
|
||||
|
||||
impl Socket for UdpSocket {
|
||||
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||
let addr = mapped_addr(parse_listen(addr, DEFAULT_PORT));
|
||||
UdpSocket::bind(addr)
|
||||
}
|
||||
|
||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||
buffer.clear();
|
||||
let (size, addr) = self.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)
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||
let mut addr = self.local_addr()?;
|
||||
addr.set_ip(get_ip());
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
fn create_port_forwarding(&self) -> Option<PortForwarding> {
|
||||
PortForwarding::new(self.address().unwrap().port())
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static MOCK_SOCKET_NAT: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
pub struct MockSocket {
|
||||
nat: bool,
|
||||
nat_peers: HashMap<SocketAddr, Time>,
|
||||
address: SocketAddr,
|
||||
outbound: VecDeque<(SocketAddr, Vec<u8>)>,
|
||||
inbound: VecDeque<(SocketAddr, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl MockSocket {
|
||||
pub fn new(address: SocketAddr) -> Self {
|
||||
Self {
|
||||
nat: Self::get_nat(),
|
||||
nat_peers: HashMap::new(),
|
||||
address,
|
||||
outbound: VecDeque::with_capacity(10),
|
||||
inbound: VecDeque::with_capacity(10),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_nat(nat: bool) {
|
||||
MOCK_SOCKET_NAT.with(|t| t.store(nat, Ordering::SeqCst))
|
||||
}
|
||||
|
||||
pub fn get_nat() -> bool {
|
||||
MOCK_SOCKET_NAT.with(|t| t.load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
pub fn put_inbound(&mut self, from: SocketAddr, data: Vec<u8>) -> bool {
|
||||
if !self.nat {
|
||||
self.inbound.push_back((from, data));
|
||||
return true;
|
||||
}
|
||||
if let Some(timeout) = self.nat_peers.get(&from) {
|
||||
if *timeout >= MockTimeSource::now() {
|
||||
self.inbound.push_back((from, data));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
warn!("Sender {:?} is filtered out by NAT", from);
|
||||
false
|
||||
}
|
||||
|
||||
pub fn pop_outbound(&mut self) -> Option<(SocketAddr, Vec<u8>)> {
|
||||
self.outbound.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for MockSocket {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Socket for MockSocket {
|
||||
fn listen(addr: &str) -> Result<Self, io::Error> {
|
||||
Ok(Self::new(mapped_addr(parse_listen(addr, DEFAULT_PORT))))
|
||||
}
|
||||
|
||||
fn receive(&mut self, buffer: &mut MsgBuffer) -> Result<SocketAddr, io::Error> {
|
||||
if let Some((addr, data)) = self.inbound.pop_front() {
|
||||
buffer.clear();
|
||||
buffer.set_length(data.len());
|
||||
buffer.message_mut().copy_from_slice(&data);
|
||||
Ok(addr)
|
||||
} else {
|
||||
Err(io::Error::new(ErrorKind::Other, "nothing in queue"))
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result<usize, io::Error> {
|
||||
self.outbound.push_back((addr, data.into()));
|
||||
if self.nat {
|
||||
self.nat_peers.insert(addr, MockTimeSource::now() + 300);
|
||||
}
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn address(&self) -> Result<SocketAddr, io::Error> {
|
||||
Ok(self.address)
|
||||
}
|
||||
|
||||
fn create_port_forwarding(&self) -> Option<PortForwarding> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
mod bench {
|
||||
use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn udp_send(b: &mut Bencher) {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let data = [0; 1400];
|
||||
let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1);
|
||||
b.iter(|| sock.send_to(&data, &addr).unwrap());
|
||||
b.bytes = 1400;
|
||||
}
|
||||
}
|
128
src/oldconfig.rs
128
src/oldconfig.rs
|
@ -1,128 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use super::{device::Type, types::Mode, util::Duration};
|
||||
use crate::config::{ConfigFile, ConfigFileBeacon, ConfigFileDevice, ConfigFileStatsd, CryptoConfig};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
pub enum OldCryptoMethod {
|
||||
#[serde(rename = "chacha20")]
|
||||
ChaCha20,
|
||||
#[serde(rename = "aes256")]
|
||||
AES256,
|
||||
#[serde(rename = "aes128")]
|
||||
AES128,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||
pub struct OldConfigFile {
|
||||
#[serde(alias = "device-type")]
|
||||
pub device_type: Option<Type>,
|
||||
#[serde(alias = "device-name")]
|
||||
pub device_name: Option<String>,
|
||||
#[serde(alias = "device-path")]
|
||||
pub device_path: Option<String>,
|
||||
pub ifup: Option<String>,
|
||||
pub ifdown: Option<String>,
|
||||
pub crypto: Option<OldCryptoMethod>,
|
||||
#[serde(alias = "shared-key")]
|
||||
pub shared_key: Option<String>,
|
||||
pub magic: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub listen: Option<String>,
|
||||
pub peers: Option<Vec<String>>,
|
||||
#[serde(alias = "peer-timeout")]
|
||||
pub peer_timeout: Option<Duration>,
|
||||
pub keepalive: Option<Duration>,
|
||||
#[serde(alias = "beacon-store")]
|
||||
pub beacon_store: Option<String>,
|
||||
#[serde(alias = "beacon-load")]
|
||||
pub beacon_load: Option<String>,
|
||||
#[serde(alias = "beacon-interval")]
|
||||
pub beacon_interval: Option<Duration>,
|
||||
pub mode: Option<Mode>,
|
||||
#[serde(alias = "dst-timeout")]
|
||||
pub dst_timeout: Option<Duration>,
|
||||
pub subnets: Option<Vec<String>>,
|
||||
#[serde(alias = "port-forwarding")]
|
||||
pub port_forwarding: Option<bool>,
|
||||
#[serde(alias = "pid-file")]
|
||||
pub pid_file: Option<String>,
|
||||
#[serde(alias = "stats-file")]
|
||||
pub stats_file: Option<String>,
|
||||
#[serde(alias = "statsd-server")]
|
||||
pub statsd_server: Option<String>,
|
||||
#[serde(alias = "statsd-prefix")]
|
||||
pub statsd_prefix: Option<String>,
|
||||
pub user: Option<String>,
|
||||
pub group: Option<String>,
|
||||
}
|
||||
|
||||
impl OldConfigFile {
|
||||
#[allow(clippy::or_fun_call)]
|
||||
pub fn convert(self) -> ConfigFile {
|
||||
if self.device_type.is_none() {
|
||||
warn!("The default device type changed from TAP to TUN")
|
||||
}
|
||||
if self.ifup.is_some() {
|
||||
info!("There is a new option --ip that can handle most use cases of --ifup")
|
||||
}
|
||||
info!("The converted config enables all available encryption algorithms");
|
||||
if self.shared_key.is_none() {
|
||||
warn!("Operation without a password is no longer supported, password set to 'none'");
|
||||
}
|
||||
if self.magic.is_some() {
|
||||
warn!("The magic header functionality is no longer supported")
|
||||
}
|
||||
if self.listen.is_some() && self.port.is_some() {
|
||||
warn!("The port option is no longer available, using listen instead")
|
||||
}
|
||||
if self.peer_timeout.is_none() {
|
||||
info!("The default peer timeout changed from 10 minutes to 5 minutes")
|
||||
}
|
||||
warn!("Even with a converted config file version 2 nodes can not communicate with version 1 nodes");
|
||||
ConfigFile {
|
||||
auto_claim: None,
|
||||
beacon: Some(ConfigFileBeacon {
|
||||
interval: self.beacon_interval,
|
||||
load: self.beacon_load,
|
||||
store: self.beacon_store,
|
||||
password: self.shared_key.clone(),
|
||||
}),
|
||||
claims: self.subnets,
|
||||
crypto: CryptoConfig {
|
||||
algorithms: vec![],
|
||||
password: Some(self.shared_key.unwrap_or_else(|| "none".to_string())),
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
trusted_keys: vec![],
|
||||
},
|
||||
device: Some(ConfigFileDevice {
|
||||
fix_rp_filter: None,
|
||||
name: self.device_name,
|
||||
path: self.device_path,
|
||||
type_: self.device_type,
|
||||
}),
|
||||
group: self.group,
|
||||
ifdown: self.ifdown,
|
||||
ifup: self.ifup,
|
||||
ip: None,
|
||||
advertise_addresses: None,
|
||||
keepalive: self.keepalive,
|
||||
listen: self.listen.or(self.port.map(|p| format!("{}", p))),
|
||||
mode: self.mode,
|
||||
peer_timeout: self.peer_timeout,
|
||||
peers: self.peers,
|
||||
pid_file: self.pid_file,
|
||||
port_forwarding: self.port_forwarding,
|
||||
stats_file: self.stats_file,
|
||||
statsd: Some(ConfigFileStatsd { prefix: self.statsd_prefix, server: self.statsd_server }),
|
||||
switch_timeout: self.dst_timeout,
|
||||
user: self.user,
|
||||
hook: None,
|
||||
hooks: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
159
src/payload.rs
159
src/payload.rs
|
@ -1,159 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use crate::{error::Error, types::Address};
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
pub trait Protocol: Sized {
|
||||
fn parse(_: &[u8]) -> Result<(Address, Address), Error>;
|
||||
}
|
||||
|
||||
/// An ethernet frame dissector
|
||||
///
|
||||
/// This dissector is able to extract the source and destination addresses of ethernet frames.
|
||||
///
|
||||
/// If the ethernet frame contains a VLAN tag, both addresses will be prefixed with that tag,
|
||||
/// resulting in 8-byte addresses. Additional nested tags will be ignored.
|
||||
pub struct Frame;
|
||||
|
||||
impl Protocol for Frame {
|
||||
/// Parses an ethernet frame and extracts the source and destination addresses
|
||||
///
|
||||
/// # Errors
|
||||
/// This method will fail when the given data is not a valid ethernet frame.
|
||||
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
||||
// HOT PATH
|
||||
let mut cursor = Cursor::new(data);
|
||||
let mut src = [0; 16];
|
||||
let mut dst = [0; 16];
|
||||
let mut proto = [0; 2];
|
||||
cursor
|
||||
.read_exact(&mut dst[..6])
|
||||
.and_then(|_| cursor.read_exact(&mut src[..6]))
|
||||
.and_then(|_| cursor.read_exact(&mut proto))
|
||||
.map_err(|_| Error::Parse("Frame is too short"))?;
|
||||
if proto == [0x81, 0x00] {
|
||||
src.copy_within(..6, 2);
|
||||
dst.copy_within(..6, 2);
|
||||
cursor.read_exact(&mut src[..2]).map_err(|_| Error::Parse("Vlan frame is too short"))?;
|
||||
src[0] &= 0x0f; // restrict vlan id to 12 bits
|
||||
dst[..2].copy_from_slice(&src[..2]);
|
||||
if src[0..1] == [0, 0] {
|
||||
// treat vlan id 0x000 as untagged
|
||||
src.copy_within(2..8, 0);
|
||||
dst.copy_within(2..8, 0);
|
||||
return Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 }));
|
||||
}
|
||||
Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 }))
|
||||
} else {
|
||||
Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 (src, dst) = Frame::parse(&data).unwrap();
|
||||
assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 6 });
|
||||
assert_eq!(dst, Address { data: [6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 6 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_frame_with_vlan() {
|
||||
let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0, 4, 210, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let (src, dst) = Frame::parse(&data).unwrap();
|
||||
assert_eq!(src, Address { data: [4, 210, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0], len: 8 });
|
||||
assert_eq!(dst, Address { data: [4, 210, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], len: 8 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_frame() {
|
||||
assert!(Frame::parse(&[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]).is_ok());
|
||||
// truncated frame
|
||||
assert!(Frame::parse(&[]).is_err());
|
||||
// truncated vlan frame
|
||||
assert!(Frame::parse(&[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0x00]).is_err());
|
||||
}
|
||||
|
||||
/// An IP packet dissector
|
||||
///
|
||||
/// This dissector is able to extract the source and destination ip addresses of ipv4 packets and
|
||||
/// ipv6 packets.
|
||||
#[allow(dead_code)]
|
||||
pub struct Packet;
|
||||
|
||||
impl Protocol for Packet {
|
||||
/// Parses an ip packet and extracts the source and destination addresses
|
||||
///
|
||||
/// # Errors
|
||||
/// This method will fail when the given data is not a valid ipv4 and ipv6 packet.
|
||||
fn parse(data: &[u8]) -> Result<(Address, Address), Error> {
|
||||
// HOT PATH
|
||||
if data.is_empty() {
|
||||
return Err(Error::Parse("Empty header"));
|
||||
}
|
||||
let version = data[0] >> 4;
|
||||
match version {
|
||||
4 => {
|
||||
if data.len() < 20 {
|
||||
return Err(Error::Parse("Truncated IPv4 header"));
|
||||
}
|
||||
let src = Address::read_from_fixed(&data[12..], 4)?;
|
||||
let dst = Address::read_from_fixed(&data[16..], 4)?;
|
||||
Ok((src, dst))
|
||||
}
|
||||
6 => {
|
||||
if data.len() < 40 {
|
||||
return Err(Error::Parse("Truncated IPv6 header"));
|
||||
}
|
||||
let src = Address::read_from_fixed(&data[8..], 16)?;
|
||||
let dst = Address::read_from_fixed(&data[24..], 16)?;
|
||||
Ok((src, dst))
|
||||
}
|
||||
_ => Err(Error::Parse("Invalid IP protocol version")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 (src, dst) = Packet::parse(&data).unwrap();
|
||||
assert_eq!(src, Address { data: [192, 168, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 4 });
|
||||
assert_eq!(dst, Address { data: [192, 168, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 4 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_ipv6_packet() {
|
||||
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,
|
||||
4, 3, 2, 1,
|
||||
];
|
||||
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!(dst, Address { data: [0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1], len: 16 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_packet() {
|
||||
assert!(Packet::parse(&[0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]).is_ok());
|
||||
assert!(Packet::parse(&[
|
||||
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
|
||||
])
|
||||
.is_ok());
|
||||
// no data
|
||||
assert!(Packet::parse(&[]).is_err());
|
||||
// wrong version
|
||||
assert!(Packet::parse(&[0x20]).is_err());
|
||||
// truncated ipv4
|
||||
assert!(Packet::parse(&[0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1]).is_err());
|
||||
// truncated ipv6
|
||||
assert!(Packet::parse(&[
|
||||
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
|
||||
])
|
||||
.is_err());
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use std::{io, os::unix::io::RawFd};
|
||||
|
||||
use super::WaitResult;
|
||||
|
||||
pub struct EpollWait {
|
||||
poll_fd: RawFd,
|
||||
event: libc::epoll_event,
|
||||
socket: RawFd,
|
||||
device: RawFd,
|
||||
timeout: u32,
|
||||
}
|
||||
|
||||
impl EpollWait {
|
||||
pub fn new(socket: RawFd, device: RawFd, timeout: u32) -> io::Result<Self> {
|
||||
Self::create(socket, device, timeout, libc::EPOLLIN as u32)
|
||||
}
|
||||
|
||||
pub fn testing(socket: RawFd, device: RawFd, timeout: u32) -> io::Result<Self> {
|
||||
Self::create(socket, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32)
|
||||
}
|
||||
|
||||
fn create(socket: RawFd, device: RawFd, timeout: u32, flags: u32) -> io::Result<Self> {
|
||||
let mut event = libc::epoll_event { u64: 0, events: 0 };
|
||||
let poll_fd = unsafe { libc::epoll_create(3) };
|
||||
if poll_fd == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
for fd in &[socket, device] {
|
||||
event.u64 = *fd as u64;
|
||||
event.events = flags;
|
||||
let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) };
|
||||
if res == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
Ok(Self { poll_fd, event, socket, device, timeout })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EpollWait {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::close(self.poll_fd) };
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for EpollWait {
|
||||
type Item = WaitResult;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(match unsafe { libc::epoll_wait(self.poll_fd, &mut self.event, 1, self.timeout as i32) } {
|
||||
-1 => WaitResult::Error(io::Error::last_os_error()),
|
||||
0 => WaitResult::Timeout,
|
||||
1 => {
|
||||
if self.event.u64 == self.socket as u64 {
|
||||
WaitResult::Socket
|
||||
} else if self.event.u64 == self.device as u64 {
|
||||
WaitResult::Device
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mod epoll;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub use self::epoll::EpollWait as WaitImpl;
|
||||
|
||||
use std::io;
|
||||
|
||||
pub enum WaitResult {
|
||||
Timeout,
|
||||
Socket,
|
||||
Device,
|
||||
Error(io::Error),
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
#[cfg(feature = "nat")]
|
||||
mod internal {
|
||||
|
||||
use std::{io, net::SocketAddrV4};
|
||||
|
||||
use igd::{search_gateway, AddAnyPortError, AddPortError, Gateway, PortMappingProtocol, SearchError};
|
||||
|
||||
use crate::util::{get_internal_ip, SystemTimeSource, Time, TimeSource};
|
||||
|
||||
const LEASE_TIME: u32 = 1800;
|
||||
|
||||
const DESCRIPTION: &str = "VpnCloud";
|
||||
|
||||
pub struct PortForwarding {
|
||||
pub internal_addr: SocketAddrV4,
|
||||
pub external_addr: SocketAddrV4,
|
||||
gateway: Gateway,
|
||||
pub next_extension: Option<Time>,
|
||||
}
|
||||
|
||||
impl PortForwarding {
|
||||
pub fn new(port: u16) -> Option<Self> {
|
||||
// Get the gateway
|
||||
let gateway = match search_gateway(Default::default()) {
|
||||
Ok(gateway) => gateway,
|
||||
Err(err) => {
|
||||
if let SearchError::IoError(ref err) = err {
|
||||
if err.kind() == io::ErrorKind::WouldBlock {
|
||||
// Why this code?
|
||||
info!("Port-forwarding: no router found");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
error!("Port-forwarding: failed to find router: {}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
debug!("Port-forwarding: found router at {}", gateway.addr);
|
||||
let internal_addr = SocketAddrV4::new(get_internal_ip(), port);
|
||||
// Query the external address
|
||||
let external_ip = match gateway.get_external_ip() {
|
||||
Ok(ip) => ip,
|
||||
Err(err) => {
|
||||
error!("Port-forwarding: failed to obtain external IP: {}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if let Ok((port, timeout)) = Self::get_any_forwarding(&gateway, internal_addr, port) {
|
||||
debug!("Port-forwarding: external IP is {}", external_ip);
|
||||
let external_addr = SocketAddrV4::new(external_ip, port);
|
||||
debug!("Port-forwarding has timeout {}", timeout);
|
||||
info!("Port-forwarding: successfully activated port forward on {}", external_addr);
|
||||
let next_extension =
|
||||
if timeout > 0 { Some(SystemTimeSource::now() + Time::from(timeout) - 60) } else { None };
|
||||
Some(PortForwarding { internal_addr, external_addr, gateway, next_extension })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_any_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
|
||||
if let Ok(a) = Self::get_forwarding(gateway, addr, port) {
|
||||
return Ok(a);
|
||||
}
|
||||
if let Ok(a) = Self::get_forwarding(gateway, addr, 0) {
|
||||
return Ok(a);
|
||||
}
|
||||
for i in 1..5 {
|
||||
if let Ok(a) = Self::get_forwarding(gateway, addr, port + i) {
|
||||
return Ok(a);
|
||||
}
|
||||
}
|
||||
for _ in 0..5 {
|
||||
if let Ok(a) = Self::get_forwarding(gateway, addr, rand::random()) {
|
||||
return Ok(a);
|
||||
}
|
||||
}
|
||||
warn!("Failed to activate port forwarding");
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn get_forwarding(gateway: &Gateway, addr: SocketAddrV4, port: u16) -> Result<(u16, u32), ()> {
|
||||
debug!("Trying external port {}", port);
|
||||
if port == 0 {
|
||||
match gateway.add_any_port(PortMappingProtocol::UDP, addr, LEASE_TIME, DESCRIPTION) {
|
||||
Ok(port) => Ok((port, LEASE_TIME)),
|
||||
Err(AddAnyPortError::OnlyPermanentLeasesSupported) => {
|
||||
match gateway.add_any_port(PortMappingProtocol::UDP, addr, 0, DESCRIPTION) {
|
||||
Ok(port) => Ok((port, 0)),
|
||||
Err(err) => {
|
||||
debug!("Port-forwarding: failed to activate port forwarding: {}", err);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Port-forwarding: failed to activate port forwarding: {}", err);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match gateway.add_port(PortMappingProtocol::UDP, port, addr, LEASE_TIME, DESCRIPTION) {
|
||||
Ok(()) => Ok((port, LEASE_TIME)),
|
||||
Err(AddPortError::OnlyPermanentLeasesSupported) => {
|
||||
match gateway.add_port(PortMappingProtocol::UDP, port, addr, 0, DESCRIPTION) {
|
||||
Ok(()) => Ok((port, 0)),
|
||||
Err(err) => {
|
||||
debug!("Port-forwarding: failed to activate port forwarding: {}", err);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Port-forwarding: failed to activate port forwarding: {}", err);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_extend(&mut self) {
|
||||
if let Some(deadline) = self.next_extension {
|
||||
if deadline > SystemTimeSource::now() {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
match self.gateway.add_port(
|
||||
PortMappingProtocol::UDP,
|
||||
self.external_addr.port(),
|
||||
self.internal_addr,
|
||||
LEASE_TIME,
|
||||
DESCRIPTION,
|
||||
) {
|
||||
Ok(()) => debug!("Port-forwarding: extended port forwarding"),
|
||||
Err(err) => debug!("Port-forwarding: failed to extend port forwarding: {}", err),
|
||||
};
|
||||
self.next_extension = Some(SystemTimeSource::now() + Time::from(LEASE_TIME) - 60);
|
||||
}
|
||||
|
||||
fn deactivate(&self) {
|
||||
match self.gateway.remove_port(PortMappingProtocol::UDP, self.external_addr.port()) {
|
||||
Ok(()) => info!("Port-forwarding: successfully deactivated port forwarding"),
|
||||
Err(err) => debug!("Port-forwarding: failed to deactivate port forwarding: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_internal_ip(&self) -> SocketAddrV4 {
|
||||
self.internal_addr
|
||||
}
|
||||
|
||||
pub fn get_external_ip(&self) -> SocketAddrV4 {
|
||||
self.external_addr
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PortForwarding {
|
||||
fn drop(&mut self) {
|
||||
self.deactivate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nat"))]
|
||||
mod internal {
|
||||
pub struct PortForwarding;
|
||||
|
||||
impl PortForwarding {
|
||||
pub fn new(_port: u16) -> Option<Self> {
|
||||
warn!("Compiled without feature 'nat', skipping port forwarding.");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn check_extend(&mut self) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use internal::*;
|
160
src/table.rs
160
src/table.rs
|
@ -1,160 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use fnv::FnvHasher;
|
||||
use std::{
|
||||
cmp::min, collections::HashMap, hash::BuildHasherDefault, io, io::Write, marker::PhantomData, net::SocketAddr,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
types::{Address, Range, RangeList},
|
||||
util::{addr_nice, Duration, Time, TimeSource},
|
||||
};
|
||||
|
||||
type Hash = BuildHasherDefault<FnvHasher>;
|
||||
|
||||
struct CacheValue {
|
||||
peer: SocketAddr,
|
||||
timeout: Time,
|
||||
}
|
||||
|
||||
struct ClaimEntry {
|
||||
peer: SocketAddr,
|
||||
claim: Range,
|
||||
timeout: Time,
|
||||
}
|
||||
|
||||
pub struct ClaimTable<TS: TimeSource> {
|
||||
cache: HashMap<Address, CacheValue, Hash>,
|
||||
cache_timeout: Duration,
|
||||
claims: Vec<ClaimEntry>,
|
||||
claim_timeout: Duration,
|
||||
_dummy: PhantomData<TS>,
|
||||
}
|
||||
|
||||
impl<TS: TimeSource> ClaimTable<TS> {
|
||||
pub fn new(cache_timeout: Duration, claim_timeout: Duration) -> Self {
|
||||
Self { cache: HashMap::default(), cache_timeout, claims: vec![], claim_timeout, _dummy: PhantomData }
|
||||
}
|
||||
|
||||
pub fn cache(&mut self, addr: Address, peer: SocketAddr) {
|
||||
// HOT PATH
|
||||
self.cache.insert(addr, CacheValue { peer, timeout: TS::now() + self.cache_timeout as Time });
|
||||
}
|
||||
|
||||
pub fn clear_cache(&mut self) {
|
||||
self.cache.clear()
|
||||
}
|
||||
|
||||
pub fn set_claims(&mut self, peer: SocketAddr, mut claims: RangeList) {
|
||||
let mut removed_claim = false;
|
||||
for entry in &mut self.claims {
|
||||
if entry.peer == peer {
|
||||
let pos = claims.iter().position(|r| r == &entry.claim);
|
||||
if let Some(pos) = pos {
|
||||
entry.timeout = TS::now() + self.claim_timeout as Time;
|
||||
claims.swap_remove(pos);
|
||||
if claims.is_empty() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
entry.timeout = 0;
|
||||
removed_claim = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for claim in claims {
|
||||
self.claims.push(ClaimEntry { peer, claim, timeout: TS::now() + self.claim_timeout as Time })
|
||||
}
|
||||
if removed_claim {
|
||||
for entry in self.cache.values_mut() {
|
||||
if entry.peer == peer {
|
||||
entry.timeout = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
self.housekeep()
|
||||
}
|
||||
|
||||
pub fn remove_claims(&mut self, peer: SocketAddr) {
|
||||
for entry in &mut self.claims {
|
||||
if entry.peer == peer {
|
||||
entry.timeout = 0
|
||||
}
|
||||
}
|
||||
for entry in self.cache.values_mut() {
|
||||
if entry.peer == peer {
|
||||
entry.timeout = 0
|
||||
}
|
||||
}
|
||||
self.housekeep()
|
||||
}
|
||||
|
||||
pub fn lookup(&mut self, addr: Address) -> Option<SocketAddr> {
|
||||
// HOT PATH
|
||||
if let Some(entry) = self.cache.get(&addr) {
|
||||
return Some(entry.peer);
|
||||
}
|
||||
// COLD PATH
|
||||
let mut found = None;
|
||||
let mut prefix_len = -1;
|
||||
for entry in &self.claims {
|
||||
if entry.claim.prefix_len as isize > prefix_len && entry.claim.matches(addr) {
|
||||
found = Some(entry);
|
||||
prefix_len = entry.claim.prefix_len as isize;
|
||||
}
|
||||
}
|
||||
if let Some(entry) = found {
|
||||
self.cache.insert(
|
||||
addr,
|
||||
CacheValue { peer: entry.peer, timeout: min(TS::now() + self.cache_timeout as Time, entry.timeout) },
|
||||
);
|
||||
return Some(entry.peer);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn housekeep(&mut self) {
|
||||
let now = TS::now();
|
||||
self.cache.retain(|_, v| v.timeout >= now);
|
||||
self.claims.retain(|e| e.timeout >= now);
|
||||
}
|
||||
|
||||
pub fn cache_len(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
|
||||
pub fn claim_len(&self) -> usize {
|
||||
self.claims.len()
|
||||
}
|
||||
|
||||
/// Write out the table
|
||||
pub fn write_out<W: Write>(&self, out: &mut W) -> Result<(), io::Error> {
|
||||
let now = TS::now();
|
||||
writeln!(out, "forwarding_table:")?;
|
||||
writeln!(out, " claims:")?;
|
||||
for entry in &self.claims {
|
||||
writeln!(
|
||||
out,
|
||||
" - \"{}\": {{ peer: \"{}\", timeout: {} }}",
|
||||
entry.claim,
|
||||
addr_nice(entry.peer),
|
||||
entry.timeout - now
|
||||
)?;
|
||||
}
|
||||
writeln!(out, " cache:")?;
|
||||
for (addr, entry) in &self.cache {
|
||||
writeln!(
|
||||
out,
|
||||
" - \"{}\": {{ peer: \"{}\", timeout: {} }}",
|
||||
addr,
|
||||
addr_nice(entry.peer),
|
||||
entry.timeout - now
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
|
@ -0,0 +1,365 @@
|
|||
use std::net::{ToSocketAddrs, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::ethernet::{Frame, SwitchTable};
|
||||
use super::ip::{RoutingTable, Packet};
|
||||
use super::types::{Protocol, Address, Range, Table};
|
||||
use super::udpmessage::{Options, Message, decode, encode};
|
||||
use super::crypto::{Crypto, CryptoMethod};
|
||||
|
||||
|
||||
impl<'a> PartialEq for Message<'a> {
|
||||
fn eq(&self, other: &Message) -> bool {
|
||||
match self {
|
||||
&Message::Data(ref data1, start1, end1) => if let &Message::Data(ref data2, start2, end2) = other {
|
||||
data1[start1..end1] == data2[start2..end2]
|
||||
} else { false },
|
||||
&Message::Peers(ref peers1) => if let &Message::Peers(ref peers2) = other {
|
||||
peers1 == peers2
|
||||
} else { false },
|
||||
&Message::Init(step1, node_id1, ref ranges1) => if let &Message::Init(step2, node_id2, ref ranges2) = other {
|
||||
step1 == step2 && node_id1 == node_id2 && ranges1 == ranges2
|
||||
} else { false },
|
||||
&Message::Close => if let &Message::Close = other {
|
||||
true
|
||||
} else { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_assignments)]
|
||||
fn udpmessage_packet() {
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::None;
|
||||
let mut payload = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,2,3,4,5,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
let mut msg = Message::Data(&mut payload, 64, 69);
|
||||
let mut buf = [0; 1024];
|
||||
let mut len = 0;
|
||||
{
|
||||
let res = encode(&mut options, &mut msg, &mut [], &mut crypto);
|
||||
assert_eq!(res.len(), 13);
|
||||
assert_eq!(&res[..8], &[118,112,110,1,0,0,0,0]);
|
||||
for i in 0..res.len() {
|
||||
buf[i] = res[i];
|
||||
}
|
||||
len = res.len();
|
||||
}
|
||||
let (options2, msg2) = decode(&mut buf[..len], &mut crypto).unwrap();
|
||||
assert_eq!(options, options2);
|
||||
assert_eq!(msg, msg2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_assignments)]
|
||||
fn udpmessage_encrypted() {
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test");
|
||||
let mut payload = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,2,3,4,5,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
let mut orig_payload = [0; 133];
|
||||
for i in 0..payload.len() {
|
||||
orig_payload[i] = payload[i];
|
||||
}
|
||||
let orig_msg = Message::Data(&mut orig_payload, 64, 69);
|
||||
let mut msg = Message::Data(&mut payload, 64, 69);
|
||||
let mut buf = [0; 1024];
|
||||
let mut len = 0;
|
||||
{
|
||||
let res = encode(&mut options, &mut msg, &mut [], &mut crypto);
|
||||
assert_eq!(res.len(), 41);
|
||||
assert_eq!(&res[..8], &[118,112,110,1,1,0,0,0]);
|
||||
for i in 0..res.len() {
|
||||
buf[i] = res[i];
|
||||
}
|
||||
len = res.len();
|
||||
}
|
||||
let (options2, msg2) = decode(&mut buf[..len], &mut crypto).unwrap();
|
||||
assert_eq!(options, options2);
|
||||
assert_eq!(orig_msg, msg2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udpmessage_peers() {
|
||||
use std::str::FromStr;
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::None;
|
||||
let mut msg = Message::Peers(vec![SocketAddr::from_str("1.2.3.4:123").unwrap(), SocketAddr::from_str("5.6.7.8:12345").unwrap(), SocketAddr::from_str("[0001:0203:0405:0607:0809:0a0b:0c0d:0e0f]:6789").unwrap()]);
|
||||
let mut should = [118,112,110,1,0,0,0,1,2,1,2,3,4,0,123,5,6,7,8,48,57,1,0,1,2,3,4,5,6,7,
|
||||
8,9,10,11,12,13,14,15,26,133];
|
||||
{
|
||||
let mut buf = [0; 1024];
|
||||
let res = encode(&mut options, &mut msg, &mut buf[..], &mut crypto);
|
||||
assert_eq!(res.len(), 40);
|
||||
for i in 0..res.len() {
|
||||
assert_eq!(res[i], should[i]);
|
||||
}
|
||||
}
|
||||
let (options2, msg2) = decode(&mut should, &mut crypto).unwrap();
|
||||
assert_eq!(options, options2);
|
||||
assert_eq!(msg, msg2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udpmessage_option_network_id() {
|
||||
let mut options = Options::default();
|
||||
options.network_id = Some(134);
|
||||
let mut crypto = Crypto::None;
|
||||
let mut msg = Message::Close;
|
||||
let mut should = [118,112,110,1,0,0,1,3,0,0,0,0,0,0,0,134];
|
||||
{
|
||||
let mut buf = [0; 1024];
|
||||
let res = encode(&mut options, &mut msg, &mut buf[..], &mut crypto);
|
||||
assert_eq!(res.len(), 16);
|
||||
assert_eq!(&res, &should);
|
||||
}
|
||||
let (options2, msg2) = decode(&mut should, &mut crypto).unwrap();
|
||||
assert_eq!(options, options2);
|
||||
assert_eq!(msg, msg2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udpmessage_init() {
|
||||
use super::types::Address;
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::None;
|
||||
let addrs = vec![Range{base: Address{data: [0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0], len: 4}, prefix_len: 24},
|
||||
Range{base: Address{data: [0,1,2,3,4,5,0,0,0,0,0,0,0,0,0,0], len: 6}, prefix_len: 16}];
|
||||
let node_id = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
|
||||
let mut msg = Message::Init(0, node_id, addrs);
|
||||
let mut should = [118,112,110,1,0,0,0,2,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,2,4,0,1,2,3,24,6,0,1,2,3,4,5,16];
|
||||
{
|
||||
let mut buf = [0; 1024];
|
||||
let res = encode(&mut options, &mut msg, &mut buf[..], &mut crypto);
|
||||
assert_eq!(res.len(), 40);
|
||||
for i in 0..res.len() {
|
||||
assert_eq!(res[i], should[i]);
|
||||
}
|
||||
}
|
||||
let (options2, msg2) = decode(&mut should, &mut crypto).unwrap();
|
||||
assert_eq!(options, options2);
|
||||
assert_eq!(msg, msg2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udpmessage_close() {
|
||||
let mut options = Options::default();
|
||||
let mut crypto = Crypto::None;
|
||||
let mut msg = Message::Close;
|
||||
let mut should = [118,112,110,1,0,0,0,3];
|
||||
{
|
||||
let mut buf = [0; 1024];
|
||||
let res = encode(&mut options, &mut msg, &mut buf[..], &mut crypto);
|
||||
assert_eq!(res.len(), 8);
|
||||
assert_eq!(&res, &should);
|
||||
}
|
||||
let (options2, msg2) = decode(&mut should, &mut crypto).unwrap();
|
||||
assert_eq!(options, options2);
|
||||
assert_eq!(msg, msg2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udpmessage_invalid() {
|
||||
let mut crypto = Crypto::None;
|
||||
assert!(decode(&mut [0x76,0x70,0x6e,1,0,0,0,0], &mut crypto).is_ok());
|
||||
// too short
|
||||
assert!(decode(&mut [], &mut crypto).is_err());
|
||||
// invalid protocol
|
||||
assert!(decode(&mut [0,1,2,0,0,0,0,0], &mut crypto).is_err());
|
||||
// invalid version
|
||||
assert!(decode(&mut [0x76,0x70,0x6e,0xaa,0,0,0,0], &mut crypto).is_err());
|
||||
// invalid crypto
|
||||
assert!(decode(&mut [0x76,0x70,0x6e,1,0xaa,0,0,0], &mut crypto).is_err());
|
||||
// invalid msg type
|
||||
assert!(decode(&mut [0x76,0x70,0x6e,1,0,0,0,0xaa], &mut crypto).is_err());
|
||||
// truncated options
|
||||
assert!(decode(&mut [0x76,0x70,0x6e,1,0,0,1,0], &mut crypto).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udpmessage_invalid_crypto() {
|
||||
let mut crypto = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test");
|
||||
// truncated crypto
|
||||
assert!(decode(&mut [0x76,0x70,0x6e,1,1,0,0,0], &mut crypto).is_err());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
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 (src, dst) = Frame::parse(&data).unwrap();
|
||||
assert_eq!(src, Address{data: [1,2,3,4,5,6,0,0,0,0,0,0,0,0,0,0], len: 6});
|
||||
assert_eq!(dst, Address{data: [6,5,4,3,2,1,0,0,0,0,0,0,0,0,0,0], len: 6});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_frame_with_vlan() {
|
||||
let data = [6,5,4,3,2,1,1,2,3,4,5,6,0x81,0,4,210,1,2,3,4,5,6,7,8];
|
||||
let (src, dst) = Frame::parse(&data).unwrap();
|
||||
assert_eq!(src, Address{data: [4,210,1,2,3,4,5,6,0,0,0,0,0,0,0,0], len: 8});
|
||||
assert_eq!(dst, Address{data: [4,210,6,5,4,3,2,1,0,0,0,0,0,0,0,0], len: 8});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_frame() {
|
||||
assert!(Frame::parse(&[6,5,4,3,2,1,1,2,3,4,5,6,1,2,3,4,5,6,7,8]).is_ok());
|
||||
// truncated frame
|
||||
assert!(Frame::parse(&[]).is_err());
|
||||
// truncated vlan frame
|
||||
assert!(Frame::parse(&[6,5,4,3,2,1,1,2,3,4,5,6,0x81,0x00]).is_err());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
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 (src, dst) = Packet::parse(&data).unwrap();
|
||||
assert_eq!(src, Address{data: [192,168,1,1,0,0,0,0,0,0,0,0,0,0,0,0], len: 4});
|
||||
assert_eq!(dst, Address{data: [192,168,1,2,0,0,0,0,0,0,0,0,0,0,0,0], len: 4});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_ipv6_packet() {
|
||||
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,4,3,2,1];
|
||||
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!(dst, Address{data: [0,9,8,7,6,5,4,3,2,1,6,5,4,3,2,1], len: 16});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_packet() {
|
||||
assert!(Packet::parse(&[0x40,0,0,0,0,0,0,0,0,0,0,0,192,168,1,1,192,168,1,2]).is_ok());
|
||||
assert!(Packet::parse(&[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]).is_ok());
|
||||
// no data
|
||||
assert!(Packet::parse(&[]).is_err());
|
||||
// wrong version
|
||||
assert!(Packet::parse(&[0x20]).is_err());
|
||||
// truncated ipv4
|
||||
assert!(Packet::parse(&[0x40,0,0,0,0,0,0,0,0,0,0,0,192,168,1,1,192,168,1]).is_err());
|
||||
// truncated ipv6
|
||||
assert!(Packet::parse(&[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]).is_err());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn switch() {
|
||||
let mut table = SwitchTable::new(10);
|
||||
let addr = Address::from_str("12:34:56:78:90:ab").unwrap();
|
||||
let peer = "1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap();
|
||||
assert!(table.lookup(&addr).is_none());
|
||||
table.learn(addr.clone(), None, peer.clone());
|
||||
assert_eq!(table.lookup(&addr), Some(peer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routing_table() {
|
||||
let mut table = RoutingTable::new();
|
||||
let peer1 = "1.2.3.4:1".to_socket_addrs().unwrap().next().unwrap();
|
||||
let peer2 = "1.2.3.4:2".to_socket_addrs().unwrap().next().unwrap();
|
||||
let peer3 = "1.2.3.4:3".to_socket_addrs().unwrap().next().unwrap();
|
||||
assert!(table.lookup(&Address::from_str("192.168.1.1").unwrap()).is_none());
|
||||
table.learn(Address::from_str("192.168.1.1").unwrap(), Some(32), peer1.clone());
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.1").unwrap()), Some(peer1));
|
||||
table.learn(Address::from_str("192.168.1.2").unwrap(), None, peer2.clone());
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.1").unwrap()), Some(peer1));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.2").unwrap()), Some(peer2));
|
||||
table.learn(Address::from_str("192.168.1.0").unwrap(), Some(24), peer3.clone());
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.1").unwrap()), Some(peer1));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.2").unwrap()), Some(peer2));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.3").unwrap()), Some(peer3));
|
||||
table.learn(Address::from_str("192.168.0.0").unwrap(), Some(16), peer1.clone());
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.2.1").unwrap()), Some(peer1));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.1").unwrap()), Some(peer1));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.2").unwrap()), Some(peer2));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.3").unwrap()), Some(peer3));
|
||||
table.learn(Address::from_str("0.0.0.0").unwrap(), Some(0), peer2.clone());
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.2.1").unwrap()), Some(peer1));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.1").unwrap()), Some(peer1));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.2").unwrap()), Some(peer2));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.1.3").unwrap()), Some(peer3));
|
||||
assert_eq!(table.lookup(&Address::from_str("1.2.3.4").unwrap()), Some(peer2));
|
||||
table.learn(Address::from_str("192.168.2.0").unwrap(), Some(27), peer3.clone());
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.2.31").unwrap()), Some(peer3));
|
||||
assert_eq!(table.lookup(&Address::from_str("192.168.2.32").unwrap()), Some(peer1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_parse_fmt() {
|
||||
assert_eq!(format!("{}", Address::from_str("120.45.22.5").unwrap()), "120.45.22.5");
|
||||
assert_eq!(format!("{}", Address::from_str("78:2d:16:05:01:02").unwrap()), "78:2d:16:05:01:02");
|
||||
assert_eq!(format!("{}", Address{data: [3,56,120,45,22,5,1,2,0,0,0,0,0,0,0,0], len: 8}), "vlan824/78:2d:16:05:01:02");
|
||||
assert_eq!(format!("{}", Address::from_str("0001:0203:0405:0607:0809:0a0b:0c0d:0e0f").unwrap()), "0001:0203:0405:0607:0809:0a0b:0c0d:0e0f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_fmt() {
|
||||
assert_eq!(format!("{:?}", Message::Data(&mut [1,2,3,4,5], 0, 5)), "Data(5 bytes)");
|
||||
assert_eq!(format!("{:?}", Message::Peers(vec![SocketAddr::from_str("1.2.3.4:123").unwrap(),
|
||||
SocketAddr::from_str("5.6.7.8:12345").unwrap(),
|
||||
SocketAddr::from_str("[0001:0203:0405:0607:0809:0a0b:0c0d:0e0f]:6789").unwrap()])),
|
||||
"Peers [1.2.3.4:123, 5.6.7.8:12345, [1:203:405:607:809:a0b:c0d:e0f]:6789]");
|
||||
assert_eq!(format!("{:?}", Message::Init(0, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], vec![
|
||||
Range{base: Address{data: [0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0], len: 4}, prefix_len: 24},
|
||||
Range{base: Address{data: [0,1,2,3,4,5,0,0,0,0,0,0,0,0,0,0], len: 6}, prefix_len: 16}
|
||||
])), "Init(stage=0, node_id=000102030405060708090a0b0c0d0e0f, [0.1.2.3/24, 00:01:02:03:04:05/16])");
|
||||
assert_eq!(format!("{:?}", Message::Close), "Close");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_decrypt_chacha20poly1305() {
|
||||
let mut sender = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test");
|
||||
let receiver = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test");
|
||||
let msg = "HelloWorld0123456789";
|
||||
let msg_bytes = msg.as_bytes();
|
||||
let mut buffer = [0u8; 1024];
|
||||
let header = [0u8; 8];
|
||||
for i in 0..msg_bytes.len() {
|
||||
buffer[i] = msg_bytes[i];
|
||||
}
|
||||
let mut nonce1 = [0u8; 12];
|
||||
let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce1, &header);
|
||||
assert_eq!(size, msg_bytes.len() + sender.additional_bytes());
|
||||
assert!(msg_bytes != &buffer[..msg_bytes.len()] as &[u8]);
|
||||
receiver.decrypt(&mut buffer[..size], &nonce1, &header).unwrap();
|
||||
assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]);
|
||||
let mut nonce2 = [0u8; 12];
|
||||
let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce2, &header);
|
||||
assert!(nonce1 != nonce2);
|
||||
receiver.decrypt(&mut buffer[..size], &nonce2, &header).unwrap();
|
||||
assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_decrypt_aes256() {
|
||||
Crypto::init();
|
||||
if ! Crypto::aes256_available() {
|
||||
return
|
||||
}
|
||||
let mut sender = Crypto::from_shared_key(CryptoMethod::AES256, "test");
|
||||
let receiver = Crypto::from_shared_key(CryptoMethod::AES256, "test");
|
||||
let msg = "HelloWorld0123456789";
|
||||
let msg_bytes = msg.as_bytes();
|
||||
let mut buffer = [0u8; 1024];
|
||||
let header = [0u8; 8];
|
||||
for i in 0..msg_bytes.len() {
|
||||
buffer[i] = msg_bytes[i];
|
||||
}
|
||||
let mut nonce1 = [0u8; 12];
|
||||
let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce1, &header);
|
||||
assert_eq!(size, msg_bytes.len() + sender.additional_bytes());
|
||||
assert!(msg_bytes != &buffer[..msg_bytes.len()] as &[u8]);
|
||||
receiver.decrypt(&mut buffer[..size], &nonce1, &header).unwrap();
|
||||
assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]);
|
||||
let mut nonce2 = [0u8; 12];
|
||||
let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce2, &header);
|
||||
assert!(nonce1 != nonce2);
|
||||
receiver.decrypt(&mut buffer[..size], &nonce2, &header).unwrap();
|
||||
assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]);
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
io::Write,
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Once,
|
||||
},
|
||||
};
|
||||
|
||||
pub use crate::{
|
||||
cloud::GenericCloud,
|
||||
config::{Config, CryptoConfig},
|
||||
device::{MockDevice, Type},
|
||||
net::MockSocket,
|
||||
payload::{Frame, Packet, Protocol},
|
||||
util::{MockTimeSource, Time, TimeSource},
|
||||
};
|
||||
|
||||
static INIT_LOGGER: Once = Once::new();
|
||||
|
||||
pub fn init_debug_logger() {
|
||||
INIT_LOGGER.call_once(|| {
|
||||
log::set_boxed_logger(Box::new(DebugLogger)).unwrap();
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
})
|
||||
}
|
||||
|
||||
static CURRENT_NODE: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
struct DebugLogger;
|
||||
|
||||
impl DebugLogger {
|
||||
pub fn set_node(node: usize) {
|
||||
CURRENT_NODE.store(node, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl log::Log for DebugLogger {
|
||||
#[inline]
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
log::max_level() > metadata.level()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log(&self, record: &log::Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
eprintln!("Node {} - {} - {}", CURRENT_NODE.load(Ordering::SeqCst), record.level(), record.args());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&self) {
|
||||
std::io::stderr().flush().expect("Failed to flush")
|
||||
}
|
||||
}
|
||||
|
||||
type TestNode<P> = GenericCloud<MockDevice, P, MockSocket, MockTimeSource>;
|
||||
|
||||
pub struct Simulator<P: Protocol> {
|
||||
next_port: u16,
|
||||
nodes: HashMap<SocketAddr, TestNode<P>>,
|
||||
messages: VecDeque<(SocketAddr, SocketAddr, Vec<u8>)>,
|
||||
}
|
||||
|
||||
pub type TapSimulator = Simulator<Frame>;
|
||||
#[allow(dead_code)]
|
||||
pub type TunSimulator = Simulator<Packet>;
|
||||
|
||||
impl<P: Protocol> Simulator<P> {
|
||||
pub fn new() -> Self {
|
||||
init_debug_logger();
|
||||
MockTimeSource::set_time(0);
|
||||
Self { next_port: 1, nodes: HashMap::default(), messages: VecDeque::with_capacity(10) }
|
||||
}
|
||||
|
||||
pub fn add_node(&mut self, nat: bool, config: &Config) -> SocketAddr {
|
||||
let mut config = config.clone();
|
||||
MockSocket::set_nat(nat);
|
||||
config.listen = format!("[::]:{}", self.next_port);
|
||||
let addr = config.listen.parse::<SocketAddr>().unwrap();
|
||||
if config.crypto.password.is_none() && config.crypto.private_key.is_none() {
|
||||
config.crypto.password = Some("test123".to_string())
|
||||
}
|
||||
DebugLogger::set_node(self.next_port as usize);
|
||||
self.next_port += 1;
|
||||
let node = TestNode::new(&config, MockSocket::new(addr), MockDevice::new(), None, None);
|
||||
DebugLogger::set_node(0);
|
||||
self.nodes.insert(addr, node);
|
||||
addr
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_node(&mut self, addr: SocketAddr) -> &mut TestNode<P> {
|
||||
let node = self.nodes.get_mut(&addr).unwrap();
|
||||
DebugLogger::set_node(node.get_num());
|
||||
node
|
||||
}
|
||||
|
||||
pub fn simulate_next_message(&mut self) {
|
||||
if let Some((src, dst, data)) = self.messages.pop_front() {
|
||||
if let Some(node) = self.nodes.get_mut(&dst) {
|
||||
if node.socket().put_inbound(src, data) {
|
||||
DebugLogger::set_node(node.get_num());
|
||||
node.trigger_socket_event();
|
||||
DebugLogger::set_node(0);
|
||||
let sock = node.socket();
|
||||
let src = dst;
|
||||
while let Some((dst, data)) = sock.pop_outbound() {
|
||||
self.messages.push_back((src, dst, data));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Message to unknown node {}", dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simulate_all_messages(&mut self) {
|
||||
while !self.messages.is_empty() {
|
||||
self.simulate_next_message()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_node_housekeep(&mut self, addr: SocketAddr) {
|
||||
let node = self.nodes.get_mut(&addr).unwrap();
|
||||
DebugLogger::set_node(node.get_num());
|
||||
node.trigger_housekeep();
|
||||
DebugLogger::set_node(0);
|
||||
let sock = node.socket();
|
||||
while let Some((dst, data)) = sock.pop_outbound() {
|
||||
self.messages.push_back((addr, dst, data));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_housekeep(&mut self) {
|
||||
for (src, node) in &mut self.nodes {
|
||||
DebugLogger::set_node(node.get_num());
|
||||
node.trigger_housekeep();
|
||||
DebugLogger::set_node(0);
|
||||
let sock = node.socket();
|
||||
while let Some((dst, data)) = sock.pop_outbound() {
|
||||
self.messages.push_back((*src, dst, data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_time(&mut self, time: Time) {
|
||||
MockTimeSource::set_time(time);
|
||||
}
|
||||
|
||||
pub fn simulate_time(&mut self, time: Time) {
|
||||
let mut t = MockTimeSource::now();
|
||||
while t < time {
|
||||
t += 1;
|
||||
self.set_time(t);
|
||||
self.trigger_housekeep();
|
||||
self.simulate_all_messages();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(&mut self, src: SocketAddr, dst: SocketAddr) {
|
||||
let node = self.nodes.get_mut(&src).unwrap();
|
||||
DebugLogger::set_node(node.get_num());
|
||||
node.connect(dst).unwrap();
|
||||
DebugLogger::set_node(0);
|
||||
let sock = node.socket();
|
||||
while let Some((dst, data)) = sock.pop_outbound() {
|
||||
self.messages.push_back((src, dst, data));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_connected(&self, src: SocketAddr, dst: SocketAddr) -> bool {
|
||||
self.nodes.get(&src).unwrap().is_connected(&dst)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn node_addresses(&self) -> Vec<SocketAddr> {
|
||||
self.nodes.keys().copied().collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn message_count(&self) -> usize {
|
||||
self.messages.len()
|
||||
}
|
||||
|
||||
pub fn put_payload(&mut self, addr: SocketAddr, data: Vec<u8>) {
|
||||
let node = self.nodes.get_mut(&addr).unwrap();
|
||||
node.device().put_inbound(data);
|
||||
DebugLogger::set_node(node.get_num());
|
||||
node.trigger_device_event();
|
||||
DebugLogger::set_node(0);
|
||||
let sock = node.socket();
|
||||
while let Some((dst, data)) = sock.pop_outbound() {
|
||||
self.messages.push_back((addr, dst, data));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_payload(&mut self, node: SocketAddr) -> Option<Vec<u8>> {
|
||||
self.nodes.get_mut(&node).unwrap().device().pop_outbound()
|
||||
}
|
||||
|
||||
pub fn drop_message(&mut self) {
|
||||
self.messages.pop_front();
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
mod common;
|
||||
mod nat;
|
||||
mod payload;
|
||||
mod peers;
|
|
@ -1,73 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use super::common::*;
|
||||
|
||||
#[test]
|
||||
fn connect_nat_2_peers() {
|
||||
let config = Config { port_forwarding: false, ..Default::default() };
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(true, &config);
|
||||
let node2 = sim.add_node(true, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.connect(node2, node1);
|
||||
|
||||
sim.simulate_time(60);
|
||||
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_nat_3_peers() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(true, &config);
|
||||
let node2 = sim.add_node(true, &config);
|
||||
let node3 = sim.add_node(true, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.connect(node2, node1);
|
||||
sim.connect(node1, node3);
|
||||
sim.connect(node3, node1);
|
||||
|
||||
sim.simulate_time(300);
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
assert!(sim.is_connected(node1, node3));
|
||||
assert!(sim.is_connected(node3, node1));
|
||||
assert!(sim.is_connected(node2, node3));
|
||||
assert!(sim.is_connected(node3, node2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nat_keepalive() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(true, &config);
|
||||
let node2 = sim.add_node(true, &config);
|
||||
let node3 = sim.add_node(true, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.connect(node2, node1);
|
||||
sim.connect(node1, node3);
|
||||
sim.connect(node3, node1);
|
||||
|
||||
sim.simulate_time(1000);
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
assert!(sim.is_connected(node1, node3));
|
||||
assert!(sim.is_connected(node3, node1));
|
||||
assert!(sim.is_connected(node2, node3));
|
||||
assert!(sim.is_connected(node3, node2));
|
||||
|
||||
sim.simulate_time(10000);
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
assert!(sim.is_connected(node1, node3));
|
||||
assert!(sim.is_connected(node3, node1));
|
||||
assert!(sim.is_connected(node2, node3));
|
||||
assert!(sim.is_connected(node3, node2));
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use super::common::*;
|
||||
|
||||
#[test]
|
||||
fn switch_delivers() {
|
||||
let config = Config { device_type: Type::Tap, ..Config::default() };
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5];
|
||||
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload), sim.pop_payload(node2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_learns() {
|
||||
let config = Config { device_type: Type::Tap, ..Config::default() };
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
let node3 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.connect(node1, node3);
|
||||
sim.connect(node2, node3);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
assert!(sim.is_connected(node1, node3));
|
||||
assert!(sim.is_connected(node3, node1));
|
||||
assert!(sim.is_connected(node2, node3));
|
||||
assert!(sim.is_connected(node3, node2));
|
||||
|
||||
let payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5];
|
||||
|
||||
// Nothing learnt so far, node1 broadcasts
|
||||
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload.clone()), sim.pop_payload(node2));
|
||||
assert_eq!(Some(payload), sim.pop_payload(node3));
|
||||
|
||||
let payload = vec![1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1];
|
||||
|
||||
// Node 2 learned the address by receiving it, does not broadcast
|
||||
|
||||
sim.put_payload(node2, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload), sim.pop_payload(node1));
|
||||
assert_eq!(None, sim.pop_payload(node3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_honours_vlans() {
|
||||
let config = Config { device_type: Type::Tap, ..Config::default() };
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
let node3 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.connect(node1, node3);
|
||||
sim.connect(node2, node3);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
assert!(sim.is_connected(node1, node3));
|
||||
assert!(sim.is_connected(node3, node1));
|
||||
assert!(sim.is_connected(node2, node3));
|
||||
assert!(sim.is_connected(node3, node2));
|
||||
|
||||
let payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0x81, 0, 0, 0x67, 1, 2, 3, 4, 5];
|
||||
|
||||
// Nothing learnt so far, node1 broadcasts
|
||||
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload.clone()), sim.pop_payload(node2));
|
||||
assert_eq!(Some(payload), sim.pop_payload(node3));
|
||||
|
||||
let payload = vec![1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x81, 0, 0, 0x67, 5, 4, 3, 2, 1];
|
||||
|
||||
// Node 2 learned the address by receiving it, does not broadcast
|
||||
|
||||
sim.put_payload(node2, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload), sim.pop_payload(node1));
|
||||
assert_eq!(None, sim.pop_payload(node3));
|
||||
|
||||
let payload = vec![1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x81, 0, 0, 0x68, 5, 4, 3, 2, 1];
|
||||
|
||||
// Different VLANs, node 2 does not learn, still broadcasts
|
||||
|
||||
sim.put_payload(node2, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload.clone()), sim.pop_payload(node1));
|
||||
assert_eq!(Some(payload), sim.pop_payload(node3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn switch_forgets() {
|
||||
// TODO Test
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_delivers() {
|
||||
let config1 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["1.1.1.1/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let config2 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["2.2.2.2/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let mut sim = TunSimulator::new();
|
||||
let node1 = sim.add_node(false, &config1);
|
||||
let node2 = sim.add_node(false, &config2);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
||||
|
||||
sim.put_payload(node1, payload.clone());
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(Some(payload), sim.pop_payload(node2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_drops_unknown_dest() {
|
||||
let config1 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["1.1.1.1/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let config2 = Config {
|
||||
device_type: Type::Tun,
|
||||
auto_claim: false,
|
||||
claims: vec!["2.2.2.2/32".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
let mut sim = TunSimulator::new();
|
||||
let node1 = sim.add_node(false, &config1);
|
||||
let node2 = sim.add_node(false, &config2);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
let payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 3, 3, 3];
|
||||
|
||||
sim.put_payload(node1, payload);
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert_eq!(None, sim.pop_payload(node2));
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
// VpnCloud - Peer-to-Peer VPN
|
||||
// Copyright (C) 2015-2021 Dennis Schwerdel
|
||||
// This software is licensed under GPL-3 or newer (see LICENSE.md)
|
||||
|
||||
use super::common::*;
|
||||
|
||||
#[test]
|
||||
fn direct_connect() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_connect_unencrypted() {
|
||||
let config = Config {
|
||||
crypto: CryptoConfig { algorithms: vec!["plain".to_string()], ..CryptoConfig::default() },
|
||||
..Config::default()
|
||||
};
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_connect() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
let node3 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.connect(node1, node3);
|
||||
sim.simulate_all_messages();
|
||||
|
||||
sim.simulate_time(120);
|
||||
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
assert!(sim.is_connected(node1, node3));
|
||||
assert!(sim.is_connected(node3, node1));
|
||||
assert!(sim.is_connected(node2, node3));
|
||||
assert!(sim.is_connected(node3, node2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_via_beacons() {
|
||||
let mut sim = TapSimulator::new();
|
||||
let beacon_path = "target/.vpncloud_test";
|
||||
let config1 = Config { beacon_store: Some(beacon_path.to_string()), ..Default::default() };
|
||||
let node1 = sim.add_node(false, &config1);
|
||||
let config2 = Config { beacon_load: Some(beacon_path.to_string()), ..Default::default() };
|
||||
let node2 = sim.add_node(false, &config2);
|
||||
|
||||
sim.set_time(100);
|
||||
sim.trigger_node_housekeep(node1);
|
||||
sim.trigger_node_housekeep(node2);
|
||||
sim.simulate_all_messages();
|
||||
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reconnect_after_timeout() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
|
||||
sim.set_time(5000);
|
||||
sim.trigger_housekeep();
|
||||
assert!(!sim.is_connected(node1, node2));
|
||||
assert!(!sim.is_connected(node2, node1));
|
||||
|
||||
sim.simulate_all_messages();
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lost_init_ping() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.drop_message(); // drop init ping
|
||||
|
||||
sim.simulate_time(120);
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lost_init_pong() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_next_message(); // init ping
|
||||
sim.drop_message(); // drop init pong
|
||||
|
||||
sim.simulate_time(120);
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lost_init_peng() {
|
||||
let config = Config::default();
|
||||
let mut sim = TapSimulator::new();
|
||||
let node1 = sim.add_node(false, &config);
|
||||
let node2 = sim.add_node(false, &config);
|
||||
|
||||
sim.connect(node1, node2);
|
||||
sim.simulate_next_message(); // init ping
|
||||
sim.simulate_next_message(); // init pong
|
||||
sim.drop_message(); // drop init peng
|
||||
|
||||
sim.simulate_time(120);
|
||||
assert!(sim.is_connected(node1, node2));
|
||||
assert!(sim.is_connected(node2, node1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn peer_exchange() {
|
||||
// TODO Test
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn lost_peer_exchange() {
|
||||
// TODO Test
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn remove_dead_peers() {
|
||||
// TODO Test
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn update_primary_address() {
|
||||
// TODO Test
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn automatic_peer_timeout() {
|
||||
// TODO Test
|
||||
unimplemented!()
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue