├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── issue_template.md ├── actions │ ├── build-deb │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── build-rpm │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ └── build-static │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh ├── dependabot.yml └── workflows │ ├── audit.yml │ └── check.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── assets ├── changelog.txt ├── deb-scripts │ └── postint ├── example.net.disabled ├── vpncloud-wsproxy.service ├── vpncloud.target └── vpncloud@.service ├── benches ├── .code.rs ├── criterion.rs └── valgrind.rs ├── contrib ├── asciinema-recorder │ ├── Dockerfile │ ├── config │ └── recorder.sh └── aws │ ├── WSProxyCloudFormation.yaml │ ├── common.py │ ├── example.py │ ├── measurements │ ├── 2020-06-08_1.0.0_perf.json │ ├── 2020-06-08_1.1.0_perf.json │ ├── 2020-06-08_1.2.0_perf.json │ ├── 2020-06-08_1.3.0_perf.json │ ├── 2020-06-08_1.4.0_perf.json │ ├── 2020-10-28_2.0.0_perf.json │ ├── 2021-02-06_2.1.0_perf.json │ ├── 2021-04-06_2.2.0_perf.json │ └── 2021-12-23_2.3.0_perf.json │ ├── performance.py │ └── testnet.py ├── maskfile.md ├── rustfmt.toml ├── src ├── beacon.rs ├── cloud.rs ├── config.rs ├── crypto │ ├── common.rs │ ├── core.rs │ ├── init.rs │ ├── mod.rs │ └── rotate.rs ├── device.rs ├── error.rs ├── installer.rs ├── main.rs ├── messages.rs ├── net.rs ├── oldconfig.rs ├── payload.rs ├── poll │ ├── epoll.rs │ └── mod.rs ├── port_forwarding.rs ├── table.rs ├── tests │ ├── common.rs │ ├── mod.rs │ ├── nat.rs │ ├── payload.rs │ └── peers.rs ├── traffic.rs ├── types.rs ├── util.rs ├── wizard.rs └── wsproxy.rs ├── vpncloud.adoc └── vpncloud.code-workspace /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [dswd] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: dswd 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/dswd73'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ### Details of your setup 14 | 15 | **VpnCloud version:** 16 | 17 | **Description of your VPN setup** 18 | - How many nodes? 19 | - Any NAT involved? 20 | - Any custom routing? 21 | 22 | **Config file contents** 23 | normally in `/etc/vpncloud/MYNET.net` 24 | :bangbang: make sure to mask (`***`) passwords and public IPs/hostnames :bangbang: 25 | 26 | **Log entries** 27 | normally in `/var/log/vpncloud.MYNET.log` 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: VpnCloud Documentation 4 | url: https://vpncloud.ddswd.de/docs 5 | about: Please check the documentation before asking how things work. 6 | - name: Discussion Group on GitHub 7 | url: https://github.com/dswd/vpncloud/discussions 8 | about: Please discuss or ask anything there that is not an issue. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ** Describe the high-level goal you want to achieve ** 11 | 12 | I would like to... 13 | 14 | ** Describe the proposed solution ** 15 | 16 | I propose to... 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other issue 3 | about: Any other issue 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe your issue 11 | 12 | 👉 Please use the [Discussion group](https://github.com/dswd/vpncloud/discussions) if you... 13 | * just want to ask a question 14 | * want to discuss a new feature 15 | -------------------------------------------------------------------------------- /.github/actions/build-deb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y --no-install-recommends \ 5 | build-essential \ 6 | curl \ 7 | gcc-aarch64-linux-gnu \ 8 | gcc-arm-linux-gnueabihf \ 9 | gcc-arm-linux-gnueabi \ 10 | libc6-dev-arm64-cross \ 11 | libc6-dev-armhf-cross \ 12 | libc6-dev-armel-cross \ 13 | libc6-dev-i386 \ 14 | gcc-5-multilib \ 15 | asciidoctor \ 16 | && apt-get clean && rm -rf /var/lib/apt/lists/* 17 | 18 | ADD entrypoint.sh /entrypoint.sh 19 | 20 | ENTRYPOINT /entrypoint.sh 21 | -------------------------------------------------------------------------------- /.github/actions/build-deb/action.yml: -------------------------------------------------------------------------------- 1 | name: 'build-deb' 2 | description: 'Create deb packages' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | -------------------------------------------------------------------------------- /.github/actions/build-deb/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TOOLCHAIN=$(grep -e '^toolchain =' Cargo.toml | sed -e 's/toolchain = "\(.*\)"/\1/') 6 | 7 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 8 | DEB_VERSION=$(echo "$VERSION" | sed -e 's/-/~/g') 9 | 10 | ln -s asm-generic/ /usr/include/asm 11 | 12 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${TOOLCHAIN} 13 | source $HOME/.cargo/env 14 | 15 | rustup target add i686-unknown-linux-gnu 16 | rustup target add armv5te-unknown-linux-gnueabi 17 | rustup target add armv7-unknown-linux-gnueabihf 18 | rustup target add aarch64-unknown-linux-gnu 19 | 20 | cargo install cargo-deb 21 | 22 | mkdir dist 23 | 24 | build_deb() { 25 | ARCH=$1 26 | TARGET=$2 27 | cargo deb --target ${TARGET} 28 | cp target/${TARGET}/debian/vpncloud_${DEB_VERSION}_${ARCH}.deb dist/vpncloud_${DEB_VERSION}_${ARCH}.deb 29 | } 30 | 31 | cargo deb 32 | cp target/debian/vpncloud_${DEB_VERSION}_amd64.deb dist/vpncloud_${DEB_VERSION}_amd64.deb 33 | 34 | build_deb i386 i686-unknown-linux-gnu 35 | build_deb armhf armv7-unknown-linux-gnueabihf 36 | build_deb armel armv5te-unknown-linux-gnueabi 37 | build_deb arm64 aarch64-unknown-linux-gnu -------------------------------------------------------------------------------- /.github/actions/build-rpm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum groupinstall -y 'Development Tools' 4 | RUN yum install -y ruby && gem install asciidoctor -v 2.0.10 5 | RUN yum install -y libstdc++-*.i686 \ 6 | && yum install -y glibc-*.i686 \ 7 | && yum install -y libgcc.i686 8 | 9 | RUN ln -s /usr/bin/gcc /usr/bin/i686-linux-gnu-gcc 10 | 11 | ADD entrypoint.sh /entrypoint.sh 12 | 13 | ENTRYPOINT /entrypoint.sh 14 | -------------------------------------------------------------------------------- /.github/actions/build-rpm/action.yml: -------------------------------------------------------------------------------- 1 | name: 'build-deb' 2 | description: 'Create deb packages' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | -------------------------------------------------------------------------------- /.github/actions/build-rpm/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TOOLCHAIN=$(grep -e '^toolchain =' Cargo.toml | sed -e 's/toolchain = "\(.*\)"/\1/') 6 | 7 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 8 | if echo "$VERSION" | fgrep -q "-"; then 9 | RPM_VERSION=$(echo "$VERSION" | sed -e 's/-/-0./g') 10 | else 11 | RPM_VERSION="$VERSION-1" 12 | fi 13 | 14 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${TOOLCHAIN} 15 | source $HOME/.cargo/env 16 | 17 | rustup target add i686-unknown-linux-gnu 18 | rustup target add armv7-unknown-linux-gnueabihf 19 | 20 | cargo install cargo-rpm 21 | 22 | mkdir dist 23 | 24 | cargo build --release 25 | cargo rpm build 26 | cp target/release/rpmbuild/RPMS/x86_64/vpncloud-${RPM_VERSION}.x86_64.rpm dist/vpncloud_${RPM_VERSION}.x86_64.rpm 27 | 28 | 29 | build_rpm() { 30 | ARCH=$1 31 | TARGET=$2 32 | if ! [ -f dist/vpncloud_${RPM_VERSION}.${ARCH}.rpm ]; then 33 | mkdir -p target 34 | [ -L target/assets ] || ln -s ../assets target/assets 35 | [ -L target/target ] || ln -s ../target target/target 36 | cargo rpm build --target ${TARGET} 37 | cp target/${TARGET}/release/rpmbuild/RPMS/${ARCH}/vpncloud-${RPM_VERSION}.${ARCH}.rpm dist/vpncloud_${RPM_VERSION}.${ARCH}.rpm 38 | fi 39 | } 40 | 41 | build_rpm i686 i686-unknown-linux-gnu -------------------------------------------------------------------------------- /.github/actions/build-static/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y --no-install-recommends \ 5 | build-essential \ 6 | curl \ 7 | gcc-aarch64-linux-gnu \ 8 | gcc-arm-linux-gnueabihf \ 9 | gcc-arm-linux-gnueabi \ 10 | libc6-dev-arm64-cross \ 11 | libc6-dev-armhf-cross \ 12 | libc6-dev-armel-cross \ 13 | libc6-dev-i386 \ 14 | gcc-5-multilib \ 15 | asciidoctor \ 16 | musl musl-dev musl-tools \ 17 | && apt-get clean && rm -rf /var/lib/apt/lists/* 18 | 19 | ADD entrypoint.sh /entrypoint.sh 20 | 21 | ENTRYPOINT /entrypoint.sh 22 | -------------------------------------------------------------------------------- /.github/actions/build-static/action.yml: -------------------------------------------------------------------------------- 1 | name: 'build-static' 2 | description: 'Create static binaries' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | -------------------------------------------------------------------------------- /.github/actions/build-static/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TOOLCHAIN=$(grep -e '^toolchain =' Cargo.toml | sed -e 's/toolchain = "\(.*\)"/\1/') 6 | UPX_VERSION=$(grep -e '^upx_version =' Cargo.toml | sed -e 's/upx_version = "\(.*\)"/\1/') 7 | 8 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 9 | DEB_VERSION=$(echo "$VERSION" | sed -e 's/-/~/g') 10 | 11 | ln -s asm-generic/ /usr/include/asm 12 | ln -s /usr/bin/aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-musl-gcc 13 | ln -s /usr/bin/arm-linux-gnueabihf-gcc /usr/bin/arm-linux-musleabihf-gcc 14 | 15 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain ${TOOLCHAIN} 16 | source $HOME/.cargo/env 17 | 18 | rustup target add x86_64-unknown-linux-musl 19 | rustup target add i686-unknown-linux-musl 20 | rustup target add armv5te-unknown-linux-musleabi 21 | rustup target add armv7-unknown-linux-musleabihf 22 | rustup target add aarch64-unknown-linux-musl 23 | 24 | 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 25 | 26 | mkdir dist 27 | 28 | build_static() { 29 | ARCH=$1 30 | TARGET=$2 31 | cargo build --release --features installer --target ${TARGET} && upx --lzma target/${TARGET}/release/vpncloud 32 | cp target/${TARGET}/release/vpncloud ../dist/vpncloud_${VERSION}_static_${ARCH} 33 | } 34 | 35 | build_static amd64 x86_64-unknown-linux-musl 36 | #build_static i386 i686-unknown-linux-musl 37 | build_static armhf armv7-unknown-linux-musleabihf 38 | build_static armel armv5te-unknown-linux-musleabi 39 | build_static arm64 aarch64-unknown-linux-musl -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | audit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions-rs/audit-check@v1 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | on: [push] 3 | jobs: 4 | check: 5 | name: Check 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions-rs/toolchain@v1 10 | with: 11 | profile: minimal 12 | toolchain: stable 13 | override: true 14 | - uses: actions-rs/cargo@v1 15 | with: 16 | command: check 17 | test: 18 | name: Test Suite 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: szenius/set-timezone@v1.0 22 | with: 23 | timezoneLinux: Europe/Berlin 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | vpncloud-oldnodes 3 | ._* 4 | .~* 5 | deb/vpncloud/vpncloud 6 | deb/vpncloud/vpncloud.1* 7 | Stats.ods 8 | dist 9 | builder/cache 10 | .idea 11 | release.sh 12 | __pycache__ 13 | *.pem 14 | .vscode -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This project follows [semantic versioning](http://semver.org). 4 | 5 | ### UNRELEASED 6 | 7 | - [changed] Changed Rust version to 1.75.0 8 | - [changed] Updated dependencies 9 | - [fixed] Fix error when IPv6 is not available 10 | 11 | ### v2.3.0 (2021-12-23) 12 | 13 | - [added] Added build for armv5te (thanks to xek) 14 | - [added] Option to specify advertised addresses 15 | - [added] Peers now learn their own address from peers 16 | - [changed] Changed Rust version to 1.57.0 17 | - [changed] Updated dependencies 18 | - [fixed] Fixed problem with IPv4 addresses in listen option 19 | - [fixed] Fixed periodic broadcast messages in switch mode 20 | 21 | ### v2.2.0 (2021-04-06) 22 | 23 | - [added] Service target file (thanks to mnhauke) 24 | - [added] Added interactive configuration wizard 25 | - [added] Support for (un-)installation 26 | - [added] Building static binaries 27 | - [added] Building i686 rpm 28 | - [changed] Restructured example config 29 | - [changed] Changed Rust version to 1.51.0 30 | - [changed] Updated dependencies 31 | - [changed] Change permissions of /etc/vpncloud 32 | 33 | ### v2.1.0 (2021-02-06) 34 | 35 | - [added] Support for websocket proxy mode 36 | - [added] Support for hook scripts to handle certain situations 37 | - [added] Support for creating shell completions 38 | - [removed] Removed dummy device type 39 | - [changed] Updated dependencies 40 | - [changed] Changed Rust version to 1.49.0 41 | - [fixed] Added missing peer address propagation 42 | - [fixed] Fixed problem with peer addresses without port 43 | 44 | ### v2.0.1 (2020-11-07) 45 | 46 | - [changed] Changed documentation 47 | - [changed] Updated dependencies 48 | - [changed] Retrying connections for 120 secs 49 | - [changed] Resetting own addresses periodically 50 | - [changed] Using smallvec everywhere 51 | - [changed] Assume default port for peers without port 52 | - [fixed] Fixed corner case with lost init message 53 | - [fixed] Do not reconnect to timed out pending connections 54 | - [fixed] Most specific claims beat less specific claims 55 | - [fixed] Count all invalid protocol traffic 56 | - [fixed] Fixed compile with musl 57 | - [fixed] Fixed time format in logs 58 | 59 | ### v2.0.0 (2020-10-30) 60 | 61 | - [added] **Add strong crypto, complete rewrite of crypto system** 62 | - [added] Automatically claim addresses based on interface addresses (disable with --no-auto-claim) 63 | - [added] Allow to give --ip instead of ifup cmd 64 | - [added] Automatically set optimal MTU on interface 65 | - [added] Warning for disabled or loose rp_filter setting 66 | - [added] Add --fix-rp-filter to fix rp filter settings 67 | - [added] Offer to migrate old configs 68 | - [changed] **Complete change of network protocol** 69 | - [changed] Negotiate crypto method per peer, select best method 70 | - [changed] Make encryption the default, no encryption must be stated explicitly 71 | - [changed] Changed default device type to TUN 72 | - [changed] Rename subnet to claim 73 | - [changed] Set peer exchange interval to 5 minutes 74 | - [changed] Periodically send claims with peer list 75 | - [changed] Changed Rust version to 1.47.0 76 | - [removed] Remove network-id parameter 77 | - [removed] Remove port config option in favor of --listen 78 | 79 | ### UNRELEASED v1.x.y 80 | 81 | - [added] Added crypto option AES128 82 | - [added] Default port for peers 83 | - [changed] Updated dependencies 84 | - [changed] Removed C code, now 100% Rust 85 | - [fixed] Fixed keepalive for small timeouts 86 | - [fixed] Fixed problem with port forwarding 87 | - [fixed] Fixed problem with TUN on dynamic host addresses 88 | 89 | ### v1.4.0 (2020-06-03) 90 | 91 | - [added] Added option to listen on specified IP 92 | - [added] Added support for statsd monitoring 93 | - [changed] No longer using two sockets for ipv4 and ipv6 94 | - [changed] Warning for missing router is now info 95 | - [changed] New warning on claimed addresses in learning mode 96 | - [changed] Rewrote argument parsing 97 | - [changed] Changed stats file format to YAML 98 | - [changed] Using asciidoc for manpage 99 | - [changed] Updated dependencies 100 | - [fixed] Fixed problem that could lead to 100% cpu consumption 101 | - [fixed] Fixed startup race condition 102 | 103 | ### v1.3.0 (2020-01-25) 104 | 105 | - [added] Building for aarch64 aka arm64 (thanks to Ivan) 106 | - [added] Added feature to disable special NAT support 107 | - [changed] Improved port forwarding on quirky routers 108 | - [changed] Reduced peer timeout to 5min to work better with NAT 109 | - [changed] Improved builder scripts 110 | - [changed] Updated dependencies 111 | - [fixed] Fixed problem with growing stats file 112 | 113 | ### v1.2.1 (2019-12-22) 114 | 115 | - [fixed] Fixed a problem with service restrictions 116 | 117 | ### v1.2.0 (2019-12-20) 118 | 119 | - [added] Added service restrictions to systemd 120 | - [changed] Rust version 1.40.0 121 | - [changed] Also drop privileges in foreground mode 122 | - [changed] Set builders to Ubuntu 16.04 and CentOS 7 123 | - [changed] Set keepalive to 120 secs when NAT is detected 124 | - [changed] Deleting beacon file at shutdown 125 | - [changed] Updated dependencies 126 | - [fixed] Added parameter keepalive to manpage 127 | - [fixed] Fixed problems on stats file when dropping permissions 128 | - [fixed] Deleting files before overwriting them 129 | - [fixed] Fixed duplicate port bindings 130 | 131 | ### v1.1.0 (2019-12-04) 132 | 133 | - [added] Exchange peer timeout and adapt keepalive accordingly 134 | - [added] Reducing published peer timeout to 5 min when NAT is detected 135 | - [added] Added more tests 136 | - [changed] Rust version 1.39.0 137 | - [changed] Updated dependencies 138 | - [fixed] Fixed potential startup dependency issue 139 | - [fixed] Fixed wrong base62 encoding 140 | 141 | ### v1.0.0 (2019-03-21) 142 | 143 | - [added] Added ability to publish small beacons for rendezvous 144 | - [added] Added build chain for packages 145 | - [added] Added more tests 146 | - [changed] Allow to build binary without manpage 147 | - [changed] Rust edition 2018 148 | - [changed] Rust version 1.33.0 149 | - [changed] Updated dependencies 150 | - [fixed] Fixed bug that could cause repeated initialization messages 151 | 152 | ### v0.9.1 (2019-02-16) 153 | 154 | - [fixed] Fixed bug in new hex secret key functionality 155 | 156 | ### v0.9.0 (2019-02-15) 157 | 158 | - [added] Added support for cross-compilation 159 | - [added] Added keepalive option for nodes behind NAT 160 | - [added] Added ability to write out statistics file with peers and traffic info 161 | - [added] Added dummy device type that does not allocate an interface 162 | - [added] Added ability to change /dev/tun path 163 | - [changed] Using ring instead of libsodium 164 | - [changed] Using PBKDF2 for shared keys (**incompatible**) 165 | - [changed] Updated dependencies 166 | - [fixed] Hashed magics now also consider first character (**incompatible**) 167 | 168 | ### v0.8.2 (2019-01-02) 169 | 170 | - [changed] Using serde instead of rustc_serialize 171 | - [changed] Updated libsodium to 1.0.16 172 | - [changed] Updated dependencies 173 | - [changed] Making clippy happy 174 | - [fixed] Fixed wrong address 175 | 176 | ### v0.8.1 (2017-05-09) 177 | 178 | - [added] Added more tests 179 | - [changed] Updated dependencies 180 | - [changed] Updated libsodium to 1.0.12 181 | - [changed] Small fixes to make clippy happy 182 | - [changed] Removed a layer of indirection from inner loop 183 | - [fixed] Fixed two problems with routing table 184 | 185 | ### v0.8.0 (2016-11-25) 186 | 187 | - [added] Support for automatic port forwarding via UPnP 188 | - [added] Added `-s` shorthand for `--subnet` 189 | - [added] Support for YAML config file via `--config` 190 | - [added] Support for running in the background 191 | - [added] Support for dropping permissions 192 | - [added] Support for writing a pid file 193 | - [added] Support for writing logs to logfile 194 | - [changed] Not overriding recently learnt addresses in switch mode 195 | - [changed] Caching resolved addresses to increase performance 196 | - [changed] Configurable magic header is now used instead of Network-ID (**incompatible**) 197 | - [changed] Clarified documentation on TUN netmasks 198 | - [changed] Added timestamps to output 199 | - [changed] Using new YAML config instead of old config files (**incompatible**) 200 | - [changed] Prefer IPv4 over IPv6 when possible 201 | - [changed] Updated dependencies 202 | - [fixed] Fixed documentation of listen parameter 203 | - [fixed] Fixed problem with multiple subnets 204 | - [fixed] Fixed problem with interrupted poll after suspend to ram 205 | - [fixed] Forgot to extend peer timeout on peer exchange 206 | - [fixed] No longer broadcasting to additional addresses 207 | 208 | ### v0.7.0 (2016-08-05) 209 | 210 | - [added] Added more tests 211 | - [added] Added pluggable polling system 212 | - [added] Added documentation 213 | - [changed] Code cleanup 214 | - [changed] Updated dependencies 215 | - [changed] Turned some clippy warnings off 216 | - [changed] Cross-compiling for ARM 217 | - [changed] Updated libsodium to 1.0.11 218 | - [removed] Removed Address remove code for prefix table 219 | - [fixed] Reconnecting to lost peers when receiving from them or sending to them 220 | - [fixed] Sending peer list more often to prevent timeouts 221 | - [fixed] Removing learnt addresses of lost peers 222 | - [fixed] Fixed possible crash in message decoding 223 | 224 | ### v0.6.0 (2016-06-02) 225 | 226 | - [added] Exponential backoff for reconnect timeouts 227 | - [added] Systemd compatible startup scripts 228 | - [changed] Repeatedly resolving connect addresses to allow DynDNS 229 | - [changed] Listening on IPv4 and IPv6 230 | - [changed] Using SO_REUSEADDR to allow frequent rebinding 231 | - [changed] Building and using local libsodium library automatically 232 | - [changed] Updated dependencies 233 | 234 | ### v0.5.0 (2016-04-05) 235 | 236 | - [added] Added license and copyright information 237 | - [added] Added documentation for daemon config files 238 | - [added] Script for performance measurements 239 | - [added] Added more tests and benchmarks 240 | - [changed] Daemon now detects network config files on its own 241 | - [changed] Using display format for addresses 242 | - [changed] Updated dependencies 243 | - [changed] New measurements 244 | - [changed] Only calling crypto_init once 245 | - [changed] Passing listen address as &str 246 | - [changed] Using FNV hash for better performance 247 | - [changed] Using slice operations instead of loops 248 | - [changed] Updated libsodium to 1.0.10 249 | - [changed] Renamed default.net to example.net 250 | - [fixed] Fixed wrong hex address formatting 251 | - [fixed] Fixed peer exchange for more than 65000 peers 252 | - [fixed] Initializing crypto for benchmarks 253 | - [fixed] Removing learned addresses of lost peers 254 | 255 | ### v0.4.3 (2016-02-02) 256 | 257 | - [changed] Updated libsodium to 1.0.8 258 | - [fixed] Fixed problem with nodes broadcasting to themselves 259 | 260 | ### v0.4.2 (2016-01-19) 261 | 262 | - [changed] Updated dependencies 263 | - [changed] New measurements 264 | - [changed] Using copy trait more often 265 | - [fixed] Fixed deb changelog 266 | 267 | ### v0.4.1 (2015-12-22) 268 | 269 | - [changed] Logging more verbosely 270 | - [fixed] Removing NULL-bytes from interface name 271 | - [fixed] Supporting hostnames as peers 272 | - [fixed] No longer encrypting multiple times 273 | - [fixed] Properly decoding protocol header when sending 274 | - [fixed] Corrected size of read data 275 | 276 | ### v0.4.0 (2015-12-22) 277 | 278 | - [added] Init script 279 | - [changed] Removed last payload memcopy 280 | - [changed] Using RNG to select peers for peers list exchange 281 | - [changed] Updated dependency versions 282 | - [changed] Updated documentation 283 | - [fixed] Printing errors instead of panics in some cases 284 | - [fixed] Build script for Debian packages 285 | 286 | ### v0.3.1 (2015-12-03) 287 | 288 | - [added] Unique node ids to avoid connecting to self (**incompatible**) 289 | - [fixed] Calling sync when writing to TUN/TAP device 290 | 291 | ### v0.3.0 (2015-12-02) 292 | 293 | - [added] Support for AES256GCM encryption 294 | - [added] Including current libsodium in builds 295 | - [added] --crypto parameter to select encryption method 296 | - [changed] Increased ChaCha20Poly1305 nonce from 8 to 12 bytes (**incompatible**) 297 | - [changed] Updated dependency versions 298 | - [changed] More tests 299 | - [changed] Removed more "unsafe" blocks 300 | - [fixed] Forgot to call `sodium_init`, huge performance increase 301 | 302 | ### v0.2.0 (2015-11-26) 303 | 304 | - [added] Sending close message at the end 305 | - [added] Support for IPv6 addresses 306 | - [added] Support for ChaCha20Poly1305 encryption 307 | - [removed] Support for ChaCha20HmacSha512256 encryption 308 | - [changed] Complete rewrite of encryption code (**incompatible**) 309 | - [changed] Removed unused code 310 | - [changed] Some speed improvements 311 | - [changed] Removed lots of "unsafe" blocks (**fixes security issue**) 312 | - [changed] Added benchmarks 313 | - [changed] Two step handshake in order to fix problems with inconsistent state 314 | - [fixed] Pretty error messages instead of panics with traces 315 | - [fixed] Pretty addresses instead of debug representation 316 | 317 | ### v0.1.0 (2015-11-25) 318 | 319 | - First release 320 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vpncloud" 3 | version = "2.4.0" 4 | authors = ["Dennis Schwerdel "] 5 | license = "GPL-3.0" 6 | description = "Peer-to-peer VPN" 7 | homepage = "https://vpncloud.ddswd.de" 8 | repository = "https://github.com/dswd/vpncloud" 9 | keywords = ["vpn", "p2p", "tun", "tap", "network"] 10 | readme = "README.md" 11 | edition = "2021" 12 | 13 | [package.metadata] 14 | toolchain = "1.75.0" 15 | upx_version = "4.2.2" 16 | 17 | [dependencies] 18 | chrono = { version = "0.4", features = ["std", "clock"], default_features = false} 19 | structopt = "0.3" 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_yaml = "0.9" 22 | log = { version = "0.4", features = ["std"] } 23 | signal = "0.7" 24 | libc = "0.2" 25 | rand = "0.8" 26 | fnv = "1" 27 | yaml-rust = "0.4" 28 | daemonize = "0.5" 29 | ring = "0.17" 30 | privdrop = "0.5" 31 | byteorder = "1.4" 32 | thiserror = "1.0" 33 | smallvec = "1.7" 34 | dialoguer = { version = "0.11", optional = true } 35 | tungstenite = { version = "0.21", optional = true } 36 | url = { version = "2.2", optional = true } 37 | igd = { version = "0.12", optional = true } 38 | 39 | 40 | [dev-dependencies] 41 | tempfile = "3" 42 | criterion = { version = "0.5", features = ["html_reports"] } 43 | iai = "0.1" 44 | 45 | [features] 46 | default = ["nat", "websocket", "wizard"] 47 | nat = ["igd"] 48 | websocket = ["tungstenite", "url"] 49 | wizard = ["dialoguer"] 50 | installer = [] 51 | 52 | [[bench]] 53 | name = "criterion" 54 | harness = false 55 | 56 | [[bench]] 57 | name = "valgrind" 58 | harness = false 59 | 60 | [profile.release] 61 | lto = true 62 | strip = true 63 | 64 | [profile.dev] 65 | lto = false 66 | 67 | [profile.test] 68 | lto = false 69 | 70 | [package.metadata.deb] 71 | extended-description = """\ 72 | VpnCloud is a high performance peer-to-peer mesh VPN over UDP supporting strong encryption, 73 | NAT traversal and a simple configuration. It establishes a fully-meshed self-healing VPN 74 | network in a peer-to-peer manner with strong end-to-end encryption based on elliptic curve 75 | keys and AES-256. VpnCloud creates a virtual network interface on the host and forwards all 76 | received data via UDP to the destination. It can work on TUN devices (IP based) and TAP 77 | devices (Ethernet based).""" 78 | license-file = ["LICENSE.md", "1"] 79 | changelog = "assets/changelog.txt" 80 | section = "net" 81 | depends = "libc6 (>= 2.23), libgcc1 (>= 1:6.0.1)" 82 | maintainer-scripts = "assets/deb-scripts" 83 | assets = [ 84 | ["target/release/vpncloud", "/usr/bin/vpncloud", "755"], 85 | ["assets/example.net.disabled", "/etc/vpncloud/example.net.disabled", "600"], 86 | ["assets/vpncloud@.service", "/lib/systemd/system/vpncloud@.service", "644"], 87 | ["assets/vpncloud.target", "/lib/systemd/system/vpncloud.target", "644"], 88 | ["assets/vpncloud-wsproxy.service", "/lib/systemd/system/vpncloud-wsproxy.service", "644"], 89 | ["target/vpncloud.1.gz", "/usr/share/man/man1/vpncloud.1.gz", "644"] 90 | ] 91 | 92 | [package.metadata.generate-rpm] 93 | assets = [ 94 | { source = "target/release/vpncloud", dest = "/usr/bin/vpncloud", mode = "755" }, 95 | { source = "assets/example.net.disabled", dest = "/etc/vpncloud/example.net.disabled", mode = "600" }, 96 | { source = "assets/vpncloud@.service", dest = "/lib/systemd/system/vpncloud@.service", mode = "644" }, 97 | { source = "assets/vpncloud.target", dest = "/lib/systemd/system/vpncloud.target", mode = "644" }, 98 | { source = "assets/vpncloud-wsproxy.service", dest = "/lib/systemd/system/vpncloud-wsproxy.service", mode = "644" }, 99 | { source = "target/vpncloud.1.gz", dest = "/usr/share/man/man1/vpncloud.1.gz", mode = "644" } 100 | ] 101 | auto-req = "no" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VpnCloud - Peer-to-Peer VPN 2 | --------------------------- 3 | ![Checks](https://github.com/dswd/vpncloud/workflows/Checks/badge.svg?branch=master) 4 | ![Security audit](https://github.com/dswd/vpncloud/workflows/Security%20audit/badge.svg?branch=master) 5 | 6 | **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). 7 | 8 | ```sh 9 | $> vpncloud -c REMOTE_HOST:PORT -p 'mypassword' --ip 10.0.0.1/24 10 | ``` 11 | 12 | or as config file: 13 | 14 | ```yaml 15 | crypto: 16 | password: mysecret 17 | ip: 10.0.0.1 18 | peers: 19 | - REMOTE_HOST:PORT 20 | ``` 21 | 22 | For more information, please see the [Website](https://vpncloud.ddswd.de) or the [Discussions group](https://github.com/dswd/vpncloud/discussions). 23 | 24 | 25 | ### Project Status 26 | This project is still [under development](CHANGELOG.md) but has reached a 27 | somewhat stable state. VpnCloud features the following functionality: 28 | 29 | * Automatic peer-to-peer meshing, no central servers 30 | * Automatic reconnecting when connections are lost 31 | * Connecting hundreds of nodes with the VPN 32 | * High throughput and low additional latency (see [performance page](https://vpncloud.ddswd.de/features/performance)) 33 | * Creating virtual network interfaces based on Ethernet (TAP) and IP (TUN) 34 | * Strong end-to-end encryption using Curve25519 key pairs and AES methods 35 | * Support for different forwarding/routing behaviors (Hub, Switch, Router) 36 | * NAT and firewall traversal using hole punching 37 | * Automatic port forwarding via UPnP 38 | * Websocket proxy mode for restrictive environments 39 | * Support for tunneled VLans (TAP devices) 40 | * Support for publishing [beacons](https://vpncloud.ddswd.de/docs/beacons) to help nodes find each others 41 | * Support for statsd monitoring 42 | * Low memory footprint 43 | * Single binary, no dependencies, no kernel module 44 | 45 | ### Installing 46 | 47 | #### Compiling from source 48 | Prerequisites: Git, [Cargo](https://www.rust-lang.org/install.html), asciidoctor 49 | 50 | 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`. 51 | 52 | The tests can be run via ``cargo test``. 53 | 54 | 55 | #### Cross-Compiling & packaging 56 | Please see the [builder folder](builder). 57 | 58 | 59 | ### Contributions welcome 60 | There are several areas in which still some work has to be done and where 61 | contributions are very welcome: 62 | 63 | * **Linux packages**: VpnCloud is stable enough to be packaged for Linux 64 | distributions. Maintainers who want to package VpnCloud are very welcome. 65 | * **Help with other platforms**: If you are a Rust developer with experience 66 | on Windows or MacOS your help on porting VpnCloud to those platforms is very 67 | welcome. 68 | * **Security review**: The security has been implemented with strong security 69 | primitives but it would be great if a cryptography expert could verify the 70 | system. 71 | * **Feedback on use cases**: Some feedback on how VpnCloud is being used and 72 | maybe some tutorials covering common use cases would be nice. 73 | 74 | 75 | ### Semantic Versioning 76 | This project uses [semantic versioning](http://semver.org). 77 | -------------------------------------------------------------------------------- /assets/deb-scripts/postint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | systemctl daemon-reload 4 | chmod 700 /etc/vpncloud -------------------------------------------------------------------------------- /assets/example.net.disabled: -------------------------------------------------------------------------------- 1 | # This configuration file uses the YAML format. 2 | # ~ means "no value" (i.e. "default value") 3 | # Replace it by a value and put quotes (") around values with special characters 4 | # List items start with a dash and a space (- ) 5 | # Note that the whitespace before the settings names is important for the file structure 6 | 7 | 8 | listen: 3210 # The port number or ip:port on which to listen for data. 9 | 10 | peers: # Address of a peer to connect to. 11 | # The address should be in the form `addr:port`. 12 | # Put [] for an empty list 13 | - node2.example.com:3210 14 | - node3.example.com:3210 15 | 16 | crypto: # Crypto settings 17 | password: ~ # <-- CHANGE # A password to encrypt the VPN data. 18 | private-key: ~ # Private key (alternative to password) 19 | public-key: ~ # Public key (alternative to password) 20 | trusted-keys: [] # Trusted keys (alternative to password) 21 | # Replace [] with list of keys 22 | 23 | ip: ~ # <-- CHANGE # An IP address to set on the device, e.g. 10.0.0.1 24 | # Must be different for every node on the VPN 25 | 26 | 27 | # ------------------ Advanced features ahead -------------------- 28 | 29 | auto-claim: true # Whether to automatically claim the configured IP on tun devices 30 | 31 | claims: # The local subnets to use. This parameter should be in the form 32 | # `address/prefixlen` where address is an IPv4 address, an IPv6 address, or a 33 | # MAC address. The prefix length is the number of significant front bits that 34 | # distinguish the subnet from other subnets. 35 | # - 10.1.1.0/24 36 | 37 | ifup: ~ # Command to setup the interface. Use $IFNAME for interface name. 38 | ifdown: ~ # Command to tear down the interface. Use $IFNAME for interface name. 39 | 40 | device: # Device settings 41 | name: "vpncloud%d" # Name of the virtual device. Any `%d` will be filled with a free number. 42 | type: tun # Set the type of network. There are two options: **tap** devices process 43 | # Ethernet frames **tun** devices process IP packets. [default: `tun`] 44 | path: "/dev/net/tun" # Path of the tun device 45 | fix-rp-filter: false # Whether to fix detected rp-filter problems 46 | 47 | mode: normal # Mode to run in, "normal", "hub", "switch", or "router" (see manpage) 48 | 49 | port-forwarding: true # Try to map a port on the router 50 | 51 | switch-timeout: 300 # Switch timeout in seconds (switch mode only) 52 | 53 | peer-timeout: 300 # Peer timeout in seconds 54 | keepalive: ~ # Keepalive interval in seconds 55 | 56 | beacon: # Beacon settings 57 | store: ~ # File or command (prefix: "|") to use for storing beacons 58 | load: ~ # File or command (prefix: "|") to use for loading beacons 59 | interval: 3600 # How often to load and store beacons (in seconds) 60 | password: ~ # Password to encrypt beacon data with 61 | 62 | statsd: # Statsd settings 63 | server: ~ # Statsd server name:port 64 | prefix: ~ # Prefix to use for stats keys 65 | 66 | pid-file: ~ # Store the process id in this file when running in the background 67 | stats-file: ~ # Periodically write statistics on peers and current traffic to the given file 68 | 69 | hook: ~ # Hook script to run for every event 70 | hooks: {} # Multiple hook scripts to run for specific events 71 | 72 | 73 | 74 | # Copy this template and save it to a file named /etc/vpncloud/MYNET.net (replace MYNET with your network name) 75 | # 76 | # On systems using systemd (most common): 77 | # start/stop the network: service vpncloud@MYNET start/stop 78 | # enable/disable automatic startup: systemctl enable/disable vpncloud@MYNET 79 | # 80 | # On older systems (using sysv init): 81 | # Add the network name to /etc/default/vpncloud 82 | # start/stop all VpnCloud networks: /etc/init.d/vpncloud start/stop -------------------------------------------------------------------------------- /assets/vpncloud-wsproxy.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=VpnCloud websocket proxy 3 | After=network-online.target 4 | Wants=network-online.target 5 | Documentation=man:vpncloud(1) 6 | 7 | [Service] 8 | Type=simple 9 | ExecStart=/usr/bin/vpncloud ws-proxy -l 3210 10 | RestartSec=5s 11 | Restart=on-failure 12 | TasksMax=10 13 | MemoryMax=50M 14 | PrivateTmp=yes 15 | ProtectHome=yes 16 | ProtectSystem=strict 17 | ReadWritePaths=/var/log /run 18 | CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SYS_CHROOT 19 | DeviceAllow=/dev/null rw 20 | 21 | [Install] 22 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /assets/vpncloud.target: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=VpnCloud target allowing to start/stop all vpncloud@.service instances at once 3 | -------------------------------------------------------------------------------- /assets/vpncloud@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=VpnCloud network '%I' 3 | After=network-online.target 4 | Wants=network-online.target 5 | PartOf=vpncloud.target 6 | Documentation=man:vpncloud(1) 7 | 8 | [Service] 9 | Type=forking 10 | 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 11 | PIDFile=/run/vpncloud-%i.pid 12 | WorkingDirectory=/etc/vpncloud 13 | RestartSec=5s 14 | Restart=on-failure 15 | TasksMax=10 16 | MemoryMax=50M 17 | PrivateTmp=yes 18 | ProtectHome=yes 19 | ProtectSystem=strict 20 | ReadWritePaths=/var/log /run 21 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT 22 | DeviceAllow=/dev/null rw 23 | DeviceAllow=/dev/net/tun rw 24 | 25 | [Install] 26 | WantedBy=multi-user.target 27 | -------------------------------------------------------------------------------- /benches/.code.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod util { 3 | include!("../src/util.rs"); 4 | } 5 | mod error { 6 | include!("../src/error.rs"); 7 | } 8 | mod payload { 9 | include!("../src/payload.rs"); 10 | } 11 | mod types { 12 | include!("../src/types.rs"); 13 | } 14 | mod table { 15 | include!("../src/table.rs"); 16 | } 17 | mod cloud { 18 | include!("../src/cloud.rs"); 19 | } 20 | mod config { 21 | include!("../src/config.rs"); 22 | } 23 | mod device { 24 | include!("../src/device.rs"); 25 | } 26 | mod net { 27 | include!("../src/net.rs"); 28 | } 29 | mod beacon { 30 | include!("../src/beacon.rs"); 31 | } 32 | mod messages { 33 | include!("../src/messages.rs"); 34 | } 35 | mod port_forwarding { 36 | include!("../src/port_forwarding.rs"); 37 | } 38 | mod traffic { 39 | include!("../src/traffic.rs"); 40 | } 41 | mod poll { 42 | pub mod epoll{ 43 | include!("../src/poll/epoll.rs"); 44 | } 45 | #[cfg(any(target_os = "linux", target_os = "android"))] 46 | pub use self::epoll::EpollWait as WaitImpl; 47 | 48 | use std::io; 49 | 50 | pub enum WaitResult { 51 | Timeout, 52 | Socket, 53 | Device, 54 | Error(io::Error) 55 | } 56 | } 57 | mod crypto { 58 | pub mod core { 59 | include!("../src/crypto/core.rs"); 60 | } 61 | pub mod init { 62 | include!("../src/crypto/init.rs"); 63 | } 64 | pub mod rotate { 65 | include!("../src/crypto/rotate.rs"); 66 | } 67 | pub mod common { 68 | include!("../src/crypto/common.rs"); 69 | } 70 | pub use common::*; 71 | pub use self::core::{EXTRA_LEN, TAG_LEN}; 72 | } 73 | mod tests { 74 | pub mod common { 75 | include!("../src/tests/common.rs"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /benches/criterion.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_macros, unused_imports)] 2 | #[macro_use] extern crate serde; 3 | #[macro_use] extern crate log; 4 | 5 | use criterion::{criterion_group, criterion_main, Criterion, Throughput}; 6 | 7 | use smallvec::smallvec; 8 | use ring::aead; 9 | 10 | use std::str::FromStr; 11 | use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket}; 12 | 13 | include!(".code.rs"); 14 | 15 | pub use error::Error; 16 | use util::{MockTimeSource, MsgBuffer}; 17 | use types::{Address, Range}; 18 | use table::ClaimTable; 19 | use device::Type; 20 | use config::Config; 21 | use payload::{Packet, Frame, Protocol}; 22 | use crypto::core::{create_dummy_pair, EXTRA_LEN}; 23 | use tests::common::{TunSimulator, TapSimulator}; 24 | 25 | fn udp_send(c: &mut Criterion) { 26 | let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); 27 | let data = [0; 1400]; 28 | let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1); 29 | let mut g = c.benchmark_group("udp_send"); 30 | g.throughput(Throughput::Bytes(1400)); 31 | g.bench_function("udp_send", |b| { 32 | b.iter(|| sock.send_to(&data, addr).unwrap()); 33 | }); 34 | g.finish(); 35 | } 36 | 37 | fn decode_ipv4(c: &mut Criterion) { 38 | let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; 39 | let mut g = c.benchmark_group("payload"); 40 | g.throughput(Throughput::Bytes(1400)); 41 | g.bench_function("decode_ipv4", |b| { 42 | b.iter(|| Packet::parse(&data).unwrap()); 43 | }); 44 | g.finish(); 45 | } 46 | 47 | fn decode_ipv6(c: &mut Criterion) { 48 | let data = [ 49 | 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, 50 | 4, 3, 2, 1 51 | ]; 52 | let mut g = c.benchmark_group("payload"); 53 | g.throughput(Throughput::Bytes(1400)); 54 | g.bench_function("decode_ipv6", |b| { 55 | b.iter(|| Packet::parse(&data).unwrap()); 56 | }); 57 | g.finish(); 58 | } 59 | 60 | fn decode_ethernet(c: &mut Criterion) { 61 | let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; 62 | let mut g = c.benchmark_group("payload"); 63 | g.throughput(Throughput::Bytes(1400)); 64 | g.bench_function("decode_ethernet", |b| { 65 | b.iter(|| Frame::parse(&data).unwrap()); 66 | }); 67 | g.finish(); 68 | } 69 | 70 | fn decode_ethernet_with_vlan(c: &mut Criterion) { 71 | 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]; 72 | let mut g = c.benchmark_group("payload"); 73 | g.throughput(Throughput::Bytes(1400)); 74 | g.bench_function("decode_ethernet_with_vlan", |b| { 75 | b.iter(|| Frame::parse(&data).unwrap()); 76 | }); 77 | g.finish(); 78 | } 79 | 80 | fn lookup_warm(c: &mut Criterion) { 81 | let mut table = ClaimTable::::new(60, 60); 82 | let addr = Address::from_str("1.2.3.4").unwrap(); 83 | table.cache(addr, SocketAddr::from_str("1.2.3.4:3210").unwrap()); 84 | let mut g = c.benchmark_group("table"); 85 | g.throughput(Throughput::Bytes(1400)); 86 | g.bench_function("lookup_warm", |b| { 87 | b.iter(|| table.lookup(addr)); 88 | }); 89 | g.finish(); 90 | } 91 | 92 | fn lookup_cold(c: &mut Criterion) { 93 | let mut table = ClaimTable::::new(60, 60); 94 | let addr = Address::from_str("1.2.3.4").unwrap(); 95 | table.set_claims(SocketAddr::from_str("1.2.3.4:3210").unwrap(), smallvec![Range::from_str("1.2.3.4/32").unwrap()]); 96 | let mut g = c.benchmark_group("table"); 97 | g.throughput(Throughput::Bytes(1400)); 98 | g.bench_function("lookup_cold", |b| { 99 | b.iter(|| { 100 | table.clear_cache(); 101 | table.lookup(addr) 102 | }); 103 | }); 104 | g.finish(); 105 | } 106 | 107 | fn crypto_bench(c: &mut Criterion, algo: &'static aead::Algorithm) { 108 | let mut buffer = MsgBuffer::new(EXTRA_LEN); 109 | buffer.set_length(1400); 110 | let (mut sender, mut receiver) = create_dummy_pair(algo); 111 | let mut g = c.benchmark_group("crypto"); 112 | g.throughput(Throughput::Bytes(2*1400)); 113 | g.bench_function(format!("{:?}", algo), |b| { 114 | b.iter(|| { 115 | sender.encrypt(&mut buffer); 116 | receiver.decrypt(&mut buffer).unwrap(); 117 | }); 118 | }); 119 | g.finish() 120 | } 121 | 122 | fn crypto_chacha20(c: &mut Criterion) { 123 | crypto_bench(c, &aead::CHACHA20_POLY1305) 124 | } 125 | 126 | fn crypto_aes128(c: &mut Criterion) { 127 | crypto_bench(c, &aead::AES_128_GCM) 128 | } 129 | 130 | fn crypto_aes256(c: &mut Criterion) { 131 | crypto_bench(c, &aead::AES_256_GCM) 132 | } 133 | 134 | fn full_communication_tun_router(c: &mut Criterion) { 135 | log::set_max_level(log::LevelFilter::Error); 136 | let config1 = Config { 137 | device_type: Type::Tun, 138 | auto_claim: false, 139 | claims: vec!["1.1.1.1/32".to_string()], 140 | ..Config::default() 141 | }; 142 | let config2 = Config { 143 | device_type: Type::Tun, 144 | auto_claim: false, 145 | claims: vec!["2.2.2.2/32".to_string()], 146 | ..Config::default() 147 | }; 148 | let mut sim = TunSimulator::new(); 149 | let node1 = sim.add_node(false, &config1); 150 | let node2 = sim.add_node(false, &config2); 151 | 152 | sim.connect(node1, node2); 153 | sim.simulate_all_messages(); 154 | assert!(sim.is_connected(node1, node2)); 155 | assert!(sim.is_connected(node2, node1)); 156 | 157 | let mut payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; 158 | payload.append(&mut vec![0; 1400]); 159 | let mut g = c.benchmark_group("full_communication"); 160 | g.throughput(Throughput::Bytes(2*1400)); 161 | g.bench_function("tun_router", |b| { 162 | b.iter(|| { 163 | sim.put_payload(node1, payload.clone()); 164 | sim.simulate_all_messages(); 165 | assert_eq!(Some(&payload), sim.pop_payload(node2).as_ref()); 166 | }); 167 | }); 168 | g.finish() 169 | } 170 | 171 | fn full_communication_tap_switch(c: &mut Criterion) { 172 | log::set_max_level(log::LevelFilter::Error); 173 | let config = Config { device_type: Type::Tap, ..Config::default() }; 174 | let mut sim = TapSimulator::new(); 175 | let node1 = sim.add_node(false, &config); 176 | let node2 = sim.add_node(false, &config); 177 | 178 | sim.connect(node1, node2); 179 | sim.simulate_all_messages(); 180 | assert!(sim.is_connected(node1, node2)); 181 | assert!(sim.is_connected(node2, node1)); 182 | 183 | let mut payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]; 184 | payload.append(&mut vec![0; 1400]); 185 | let mut g = c.benchmark_group("full_communication"); 186 | g.throughput(Throughput::Bytes(2*1400)); 187 | g.bench_function("tap_switch", |b| { 188 | b.iter(|| { 189 | sim.put_payload(node1, payload.clone()); 190 | sim.simulate_all_messages(); 191 | assert_eq!(Some(&payload), sim.pop_payload(node2).as_ref()); 192 | }); 193 | }); 194 | g.finish() 195 | } 196 | 197 | criterion_group!(benches, 198 | udp_send, 199 | decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, 200 | lookup_cold, lookup_warm, 201 | crypto_chacha20, crypto_aes128, crypto_aes256, 202 | full_communication_tun_router, full_communication_tap_switch 203 | ); 204 | criterion_main!(benches); -------------------------------------------------------------------------------- /benches/valgrind.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_macros, unused_imports)] 2 | #[macro_use] extern crate serde; 3 | #[macro_use] extern crate log; 4 | 5 | use iai::{black_box, main}; 6 | 7 | use smallvec::smallvec; 8 | use ring::aead; 9 | 10 | use std::str::FromStr; 11 | use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4, UdpSocket}; 12 | 13 | include!(".code.rs"); 14 | 15 | pub use error::Error; 16 | use util::{MockTimeSource, MsgBuffer}; 17 | use config::Config; 18 | use types::{Address, Range}; 19 | use device::Type; 20 | use table::ClaimTable; 21 | use payload::{Packet, Frame, Protocol}; 22 | use crypto::core::{create_dummy_pair, EXTRA_LEN}; 23 | use tests::common::{TunSimulator, TapSimulator}; 24 | 25 | fn udp_send() { 26 | let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); 27 | let data = [0; 1400]; 28 | let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1); 29 | sock.send_to(&data, black_box(addr)).unwrap(); 30 | } 31 | 32 | fn decode_ipv4() { 33 | let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; 34 | Packet::parse(&black_box(data)).unwrap(); 35 | } 36 | 37 | fn decode_ipv6() { 38 | let data = [ 39 | 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, 40 | 4, 3, 2, 1 41 | ]; 42 | Packet::parse(&black_box(data)).unwrap(); 43 | } 44 | 45 | fn decode_ethernet() { 46 | let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; 47 | Frame::parse(&black_box(data)).unwrap(); 48 | } 49 | 50 | fn decode_ethernet_with_vlan() { 51 | 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]; 52 | Frame::parse(&black_box(data)).unwrap(); 53 | } 54 | 55 | fn lookup_warm() { 56 | let mut table = ClaimTable::::new(60, 60); 57 | let addr = Address::from_str("1.2.3.4").unwrap(); 58 | table.cache(addr, SocketAddr::from_str("1.2.3.4:3210").unwrap()); 59 | for _ in 0..1000 { 60 | table.lookup(black_box(addr)); 61 | } 62 | } 63 | 64 | fn lookup_cold() { 65 | let mut table = ClaimTable::::new(60, 60); 66 | let addr = Address::from_str("1.2.3.4").unwrap(); 67 | table.set_claims(SocketAddr::from_str("1.2.3.4:3210").unwrap(), smallvec![Range::from_str("1.2.3.4/32").unwrap()]); 68 | for _ in 0..1000 { 69 | table.clear_cache(); 70 | table.lookup(black_box(addr)); 71 | } 72 | } 73 | 74 | fn crypto_bench(algo: &'static aead::Algorithm) { 75 | let mut buffer = MsgBuffer::new(EXTRA_LEN); 76 | buffer.set_length(1400); 77 | let (mut sender, mut receiver) = create_dummy_pair(algo); 78 | for _ in 0..1000 { 79 | sender.encrypt(black_box(&mut buffer)); 80 | receiver.decrypt(&mut buffer).unwrap(); 81 | } 82 | } 83 | 84 | fn crypto_chacha20() { 85 | crypto_bench(&aead::CHACHA20_POLY1305) 86 | } 87 | 88 | fn crypto_aes128() { 89 | crypto_bench(&aead::AES_128_GCM) 90 | } 91 | 92 | fn crypto_aes256() { 93 | crypto_bench(&aead::AES_256_GCM) 94 | } 95 | 96 | fn full_communication_tun_router() { 97 | log::set_max_level(log::LevelFilter::Error); 98 | let config1 = Config { 99 | device_type: Type::Tun, 100 | auto_claim: false, 101 | claims: vec!["1.1.1.1/32".to_string()], 102 | ..Config::default() 103 | }; 104 | let config2 = Config { 105 | device_type: Type::Tun, 106 | auto_claim: false, 107 | claims: vec!["2.2.2.2/32".to_string()], 108 | ..Config::default() 109 | }; 110 | let mut sim = TunSimulator::new(); 111 | let node1 = sim.add_node(false, &config1); 112 | let node2 = sim.add_node(false, &config2); 113 | 114 | sim.connect(node1, node2); 115 | sim.simulate_all_messages(); 116 | assert!(sim.is_connected(node1, node2)); 117 | assert!(sim.is_connected(node2, node1)); 118 | 119 | let mut payload = vec![0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; 120 | payload.append(&mut vec![0; 1400]); 121 | for _ in 0..1000 { 122 | sim.put_payload(node1, payload.clone()); 123 | sim.simulate_all_messages(); 124 | assert_eq!(Some(&payload), black_box(sim.pop_payload(node2).as_ref())); 125 | } 126 | } 127 | 128 | fn full_communication_tap_switch() { 129 | log::set_max_level(log::LevelFilter::Error); 130 | let config = Config { device_type: Type::Tap, ..Config::default() }; 131 | let mut sim = TapSimulator::new(); 132 | let node1 = sim.add_node(false, &config); 133 | let node2 = sim.add_node(false, &config); 134 | 135 | sim.connect(node1, node2); 136 | sim.simulate_all_messages(); 137 | assert!(sim.is_connected(node1, node2)); 138 | assert!(sim.is_connected(node2, node1)); 139 | 140 | let mut payload = vec![2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5]; 141 | payload.append(&mut vec![0; 1400]); 142 | for _ in 0..1000 { 143 | sim.put_payload(node1, payload.clone()); 144 | sim.simulate_all_messages(); 145 | assert_eq!(Some(&payload), black_box(sim.pop_payload(node2).as_ref())); 146 | } 147 | } 148 | 149 | iai::main!( 150 | udp_send, 151 | decode_ipv4, decode_ipv6, decode_ethernet, decode_ethernet_with_vlan, 152 | lookup_cold, lookup_warm, 153 | crypto_chacha20, crypto_aes128, crypto_aes256, 154 | full_communication_tun_router, full_communication_tap_switch 155 | ); -------------------------------------------------------------------------------- /contrib/asciinema-recorder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update && apt-get install -y asciinema locales bash iputils-ping 4 | RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen 5 | ENV LANG en_US.UTF-8 6 | ENV LANGUAGE en_US:en 7 | ENV LC_ALL en_US.UTF-8 8 | RUN mkdir /root/.asciinema 9 | RUN mkdir /etc/vpncloud 10 | 11 | WORKDIR /data 12 | ADD config /root/.asciinema/config 13 | 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 14 | -------------------------------------------------------------------------------- /contrib/asciinema-recorder/config: -------------------------------------------------------------------------------- 1 | [record] 2 | command = bash -l 3 | idle_time_limit = 2.5 4 | -------------------------------------------------------------------------------- /contrib/asciinema-recorder/recorder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd $(dirname $0) 6 | 7 | docker build -t asciinema-recorder . 8 | docker run -it --rm --network host \ 9 | -v $(pwd):/data \ 10 | -v /etc/hosts:/etc/hosts \ 11 | asciinema-recorder 12 | -------------------------------------------------------------------------------- /contrib/aws/WSProxyCloudFormation.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | VpnCloud Websocket Proxy 4 | This will configure a websocket proxy to be used with VpnCloud. 5 | Versions: Ubuntu Server 20.04 LTS + VpnCloud 2.1.0 6 | Parameters: 7 | LatestAmiId: 8 | Description: "Image to use (just leave this as it is)" 9 | Type: 'AWS::SSM::Parameter::Value' 10 | Default: '/aws/service/canonical/ubuntu/server/20.04/stable/current/arm64/hvm/ebs-gp2/ami-id' 11 | AllowedValues: 12 | - '/aws/service/canonical/ubuntu/server/20.04/stable/current/arm64/hvm/ebs-gp2/ami-id' 13 | Resources: 14 | ProxySecurityGroup: 15 | Type: 'AWS::EC2::SecurityGroup' 16 | Properties: 17 | GroupDescription: Enable HTTP access via port 80 and any UDP port 18 | SecurityGroupIngress: 19 | - IpProtocol: tcp 20 | FromPort: '80' 21 | ToPort: '80' 22 | CidrIp: 0.0.0.0/0 23 | - IpProtocol: udp 24 | FromPort: '1024' 25 | ToPort: '65535' 26 | CidrIp: 0.0.0.0/0 27 | LaunchTemplate: 28 | Type: AWS::EC2::LaunchTemplate 29 | DependsOn: 30 | - ProxySecurityGroup 31 | Properties: 32 | LaunchTemplateData: 33 | ImageId: !Ref LatestAmiId 34 | SecurityGroups: 35 | - !Ref ProxySecurityGroup 36 | InstanceMarketOptions: 37 | MarketType: spot 38 | InstanceType: t4g.nano 39 | TagSpecifications: 40 | - ResourceType: instance 41 | Tags: 42 | - Key: Name 43 | Value: VpnCloud WS Proxy 44 | CreditSpecification: 45 | CpuCredits: standard 46 | BlockDeviceMappings: 47 | - DeviceName: /dev/sda1 48 | Ebs: 49 | VolumeType: standard 50 | VolumeSize: '8' 51 | DeleteOnTermination: 'true' 52 | Encrypted: 'false' 53 | UserData: 54 | Fn::Base64: !Sub | 55 | #cloud-config 56 | packages: 57 | - iperf3 58 | - socat 59 | runcmd: 60 | - wget https://github.com/dswd/vpncloud/releases/download/v2.1.0/vpncloud_2.1.0_arm64.deb -O /tmp/vpncloud.deb 61 | - dpkg -i /tmp/vpncloud.deb 62 | - nohup vpncloud ws-proxy -l 80 & 63 | ProxyInstance: 64 | Type: 'AWS::EC2::Instance' 65 | DependsOn: 66 | - LaunchTemplate 67 | Properties: 68 | LaunchTemplate: 69 | LaunchTemplateId: 70 | Ref: LaunchTemplate 71 | Version: 1 72 | Outputs: 73 | ProxyURL: 74 | Description: URL to use in VpnCloud config 75 | Value: !Join 76 | - '' 77 | - - 'ws://' 78 | - !GetAtt 79 | - ProxyInstance 80 | - PublicDnsName 81 | - ':80' 82 | -------------------------------------------------------------------------------- /contrib/aws/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from common import EC2Environment, CREATE 4 | import time 5 | 6 | setup = EC2Environment( 7 | region = "eu-central-1", 8 | node_count = 2, 9 | instance_type = 't3a.nano', 10 | vpncloud_version = "2.1.0" 11 | ) 12 | 13 | sender = setup.nodes[0] 14 | receiver = setup.nodes[1] 15 | 16 | sender.start_vpncloud(ip="10.0.0.1/24") 17 | receiver.start_vpncloud(ip="10.0.0.2/24", peers=["{}:3210".format(sender.private_ip)]) 18 | time.sleep(1.0) 19 | 20 | sender.ping("10.0.0.2") 21 | 22 | sender.stop_vpncloud() 23 | receiver.stop_vpncloud() -------------------------------------------------------------------------------- /contrib/aws/measurements/2020-06-08_1.0.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0a02ee601d742e89f", 6 | "version": "1.0.0", 7 | "duration": 495.34057664871216 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9680305000.0, 12 | "cpu_sender": 20.862659, 13 | "cpu_receiver": 65.856166 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.035, 17 | "rtt_max": 0.121, 18 | "rtt_avg": 0.04, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.036, 23 | "rtt_max": 0.151, 24 | "rtt_avg": 0.042, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.039, 29 | "rtt_max": 0.145, 30 | "rtt_avg": 0.043, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 4836527000.0, 37 | "cpu_sender": 9.380104, 38 | "cpu_receiver": 65.333537 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.06, 42 | "rtt_max": 0.165, 43 | "rtt_avg": 0.074, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.063, 48 | "rtt_max": 0.389, 49 | "rtt_avg": 0.076, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.066, 54 | "rtt_max": 0.185, 55 | "rtt_avg": 0.077, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3312712000.0, 62 | "cpu_sender": 7.639525, 63 | "cpu_receiver": 54.563243 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.062, 67 | "rtt_max": 1.352, 68 | "rtt_avg": 0.075, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.064, 73 | "rtt_max": 0.163, 74 | "rtt_avg": 0.077, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.067, 79 | "rtt_max": 0.334, 80 | "rtt_avg": 0.079, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "chacha20": { 85 | "iperf": { 86 | "throughput": 2418750000.0, 87 | "cpu_sender": 4.331642, 88 | "cpu_receiver": 49.452792 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.065, 92 | "rtt_max": 0.182, 93 | "rtt_avg": 0.08, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.072, 98 | "rtt_max": 0.201, 99 | "rtt_avg": 0.086, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.075, 104 | "rtt_max": 0.38, 105 | "rtt_avg": 0.09, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "results": { 110 | "throughput_mbits": { 111 | "native": 9680.305, 112 | "plain": 4836.527, 113 | "aes256": 3312.712, 114 | "chacha20": 2418.75 115 | }, 116 | "latency_us": { 117 | "plain": { 118 | "100": 16.999999999999996, 119 | "500": 16.999999999999996, 120 | "1000": 17.0 121 | }, 122 | "aes256": { 123 | "100": 17.499999999999996, 124 | "500": 17.499999999999996, 125 | "1000": 18.000000000000004 126 | }, 127 | "chacha20": { 128 | "100": 20.0, 129 | "500": 21.999999999999996, 130 | "1000": 23.5 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2020-06-08_1.1.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0a02ee601d742e89f", 6 | "version": "1.1.0", 7 | "duration": 495.3976471424103 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9672876000.0, 12 | "cpu_sender": 21.627575, 13 | "cpu_receiver": 68.637173 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.049, 17 | "rtt_max": 0.869, 18 | "rtt_avg": 0.057, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.051, 23 | "rtt_max": 13.136, 24 | "rtt_avg": 0.059, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.054, 29 | "rtt_max": 0.179, 30 | "rtt_avg": 0.06, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 4983797000.0, 37 | "cpu_sender": 11.27702, 38 | "cpu_receiver": 65.580003 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.077, 42 | "rtt_max": 0.24, 43 | "rtt_avg": 0.093, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.078, 48 | "rtt_max": 13.188, 49 | "rtt_avg": 0.096, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.083, 54 | "rtt_max": 0.223, 55 | "rtt_avg": 0.097, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3226869000.0, 62 | "cpu_sender": 5.683309, 63 | "cpu_receiver": 54.09244 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.077, 67 | "rtt_max": 1.533, 68 | "rtt_avg": 0.095, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.082, 73 | "rtt_max": 13.21, 74 | "rtt_avg": 0.098, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.084, 79 | "rtt_max": 0.249, 80 | "rtt_avg": 0.099, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "chacha20": { 85 | "iperf": { 86 | "throughput": 2740356000.0, 87 | "cpu_sender": 7.041418, 88 | "cpu_receiver": 56.705647 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.081, 92 | "rtt_max": 0.299, 93 | "rtt_avg": 0.096, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.087, 98 | "rtt_max": 13.089, 99 | "rtt_avg": 0.104, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.092, 104 | "rtt_max": 0.36, 105 | "rtt_avg": 0.108, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "results": { 110 | "throughput_mbits": { 111 | "native": 9672.876, 112 | "plain": 4983.797, 113 | "aes256": 3226.869, 114 | "chacha20": 2740.356 115 | }, 116 | "latency_us": { 117 | "plain": { 118 | "100": 18.0, 119 | "500": 18.500000000000004, 120 | "1000": 18.500000000000004 121 | }, 122 | "aes256": { 123 | "100": 19.0, 124 | "500": 19.500000000000004, 125 | "1000": 19.500000000000004 126 | }, 127 | "chacha20": { 128 | "100": 19.5, 129 | "500": 22.5, 130 | "1000": 24.0 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2020-06-08_1.2.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0a02ee601d742e89f", 6 | "version": "1.2.0", 7 | "duration": 495.3082287311554 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9679928000.0, 12 | "cpu_sender": 14.518989, 13 | "cpu_receiver": 75.510689 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.044, 17 | "rtt_max": 0.212, 18 | "rtt_avg": 0.052, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.049, 23 | "rtt_max": 0.368, 24 | "rtt_avg": 0.053, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.049, 29 | "rtt_max": 0.422, 30 | "rtt_avg": 0.055, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 5670752000.0, 37 | "cpu_sender": 15.711913, 38 | "cpu_receiver": 69.661585 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.076, 42 | "rtt_max": 0.298, 43 | "rtt_avg": 0.092, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.079, 48 | "rtt_max": 0.269, 49 | "rtt_avg": 0.094, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.079, 54 | "rtt_max": 0.469, 55 | "rtt_avg": 0.095, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3531058000.0, 62 | "cpu_sender": 5.652232, 63 | "cpu_receiver": 58.238361 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.08, 67 | "rtt_max": 0.849, 68 | "rtt_avg": 0.095, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.08, 73 | "rtt_max": 0.205, 74 | "rtt_avg": 0.096, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.083, 79 | "rtt_max": 0.418, 80 | "rtt_avg": 0.099, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "chacha20": { 85 | "iperf": { 86 | "throughput": 2384829000.0, 87 | "cpu_sender": 3.995289, 88 | "cpu_receiver": 47.656852 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.08, 92 | "rtt_max": 0.2, 93 | "rtt_avg": 0.096, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.087, 98 | "rtt_max": 0.223, 99 | "rtt_avg": 0.102, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.092, 104 | "rtt_max": 0.416, 105 | "rtt_avg": 0.109, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "results": { 110 | "throughput_mbits": { 111 | "native": 9679.928, 112 | "plain": 5670.752, 113 | "aes256": 3531.058, 114 | "chacha20": 2384.829 115 | }, 116 | "latency_us": { 117 | "plain": { 118 | "100": 20.0, 119 | "500": 20.5, 120 | "1000": 20.0 121 | }, 122 | "aes256": { 123 | "100": 21.5, 124 | "500": 21.5, 125 | "1000": 22.000000000000004 126 | }, 127 | "chacha20": { 128 | "100": 22.000000000000004, 129 | "500": 24.499999999999996, 130 | "1000": 27.0 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2020-06-08_1.3.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0a02ee601d742e89f", 6 | "version": "1.3.0", 7 | "duration": 495.4212408065796 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9673825000.0, 12 | "cpu_sender": 14.002781, 13 | "cpu_receiver": 74.93156 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.04, 17 | "rtt_max": 0.235, 18 | "rtt_avg": 0.047, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.042, 23 | "rtt_max": 0.161, 24 | "rtt_avg": 0.048, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.043, 29 | "rtt_max": 0.36, 30 | "rtt_avg": 0.049, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 5335762000.0, 37 | "cpu_sender": 14.483975, 38 | "cpu_receiver": 66.013613 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.069, 42 | "rtt_max": 0.183, 43 | "rtt_avg": 0.084, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.074, 48 | "rtt_max": 0.185, 49 | "rtt_avg": 0.088, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.077, 54 | "rtt_max": 0.203, 55 | "rtt_avg": 0.089, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3488220000.0, 62 | "cpu_sender": 5.49698, 63 | "cpu_receiver": 57.458403 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.073, 67 | "rtt_max": 1.55, 68 | "rtt_avg": 0.087, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.078, 73 | "rtt_max": 3.637, 74 | "rtt_avg": 0.091, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.079, 79 | "rtt_max": 0.233, 80 | "rtt_avg": 0.092, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "chacha20": { 85 | "iperf": { 86 | "throughput": 2784941000.0, 87 | "cpu_sender": 4.273576, 88 | "cpu_receiver": 59.274818 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.073, 92 | "rtt_max": 9.541, 93 | "rtt_avg": 0.092, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.084, 98 | "rtt_max": 0.196, 99 | "rtt_avg": 0.098, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.089, 104 | "rtt_max": 0.614, 105 | "rtt_avg": 0.104, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "results": { 110 | "throughput_mbits": { 111 | "native": 9673.825, 112 | "plain": 5335.762, 113 | "aes256": 3488.22, 114 | "chacha20": 2784.941 115 | }, 116 | "latency_us": { 117 | "plain": { 118 | "100": 18.500000000000004, 119 | "500": 19.999999999999996, 120 | "1000": 19.999999999999996 121 | }, 122 | "aes256": { 123 | "100": 19.999999999999996, 124 | "500": 21.5, 125 | "1000": 21.5 126 | }, 127 | "chacha20": { 128 | "100": 22.5, 129 | "500": 25.0, 130 | "1000": 27.499999999999996 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2020-06-08_1.4.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0a02ee601d742e89f", 6 | "version": "1.4.0", 7 | "duration": 495.3894383907318 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9679070000.0, 12 | "cpu_sender": 12.871267, 13 | "cpu_receiver": 71.50818 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.039, 17 | "rtt_max": 0.219, 18 | "rtt_avg": 0.047, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.041, 23 | "rtt_max": 0.184, 24 | "rtt_avg": 0.048, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.042, 29 | "rtt_max": 0.303, 30 | "rtt_avg": 0.049, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 4991320000.0, 37 | "cpu_sender": 8.543496, 38 | "cpu_receiver": 65.730322 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.071, 42 | "rtt_max": 3.557, 43 | "rtt_avg": 0.087, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.075, 48 | "rtt_max": 0.726, 49 | "rtt_avg": 0.088, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.076, 54 | "rtt_max": 3.38, 55 | "rtt_avg": 0.091, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3824049000.0, 62 | "cpu_sender": 8.673973, 63 | "cpu_receiver": 62.240793 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.072, 67 | "rtt_max": 1.365, 68 | "rtt_avg": 0.09, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.078, 73 | "rtt_max": 0.236, 74 | "rtt_avg": 0.092, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.079, 79 | "rtt_max": 0.439, 80 | "rtt_avg": 0.094, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "chacha20": { 85 | "iperf": { 86 | "throughput": 2838553000.0, 87 | "cpu_sender": 6.9273, 88 | "cpu_receiver": 60.482437 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.075, 92 | "rtt_max": 0.783, 93 | "rtt_avg": 0.093, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.086, 98 | "rtt_max": 0.208, 99 | "rtt_avg": 0.098, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.088, 104 | "rtt_max": 0.214, 105 | "rtt_avg": 0.103, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "results": { 110 | "throughput_mbits": { 111 | "native": 9679.07, 112 | "plain": 4991.32, 113 | "aes256": 3824.049, 114 | "chacha20": 2838.553 115 | }, 116 | "latency_us": { 117 | "plain": { 118 | "100": 19.999999999999996, 119 | "500": 19.999999999999996, 120 | "1000": 20.999999999999996 121 | }, 122 | "aes256": { 123 | "100": 21.5, 124 | "500": 22.0, 125 | "1000": 22.5 126 | }, 127 | "chacha20": { 128 | "100": 23.0, 129 | "500": 25.0, 130 | "1000": 26.999999999999996 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2020-10-28_2.0.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-00a205cb8e06c3c4e", 6 | "version": "2.0.0", 7 | "duration": 621.3780446052551 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9681224000.0, 12 | "cpu_sender": 13.679709, 13 | "cpu_receiver": 71.69651 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.045, 17 | "rtt_max": 0.18, 18 | "rtt_avg": 0.051, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.047, 23 | "rtt_max": 0.184, 24 | "rtt_avg": 0.054, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.049, 29 | "rtt_max": 0.175, 30 | "rtt_avg": 0.056, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 5472962000.0, 37 | "cpu_sender": 15.087884, 38 | "cpu_receiver": 67.570992 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.078, 42 | "rtt_max": 0.257, 43 | "rtt_avg": 0.093, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.08, 48 | "rtt_max": 0.243, 49 | "rtt_avg": 0.097, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.08, 54 | "rtt_max": 0.591, 55 | "rtt_avg": 0.096, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3947676000.0, 62 | "cpu_sender": 6.859741, 63 | "cpu_receiver": 62.826154 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.081, 67 | "rtt_max": 1.653, 68 | "rtt_avg": 0.096, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.081, 73 | "rtt_max": 1.259, 74 | "rtt_avg": 0.098, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.082, 79 | "rtt_max": 0.257, 80 | "rtt_avg": 0.099, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "aes128": { 85 | "iperf": { 86 | "throughput": 4200596000.0, 87 | "cpu_sender": 10.291266, 88 | "cpu_receiver": 64.395908 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.081, 92 | "rtt_max": 0.294, 93 | "rtt_avg": 0.097, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.084, 98 | "rtt_max": 0.238, 99 | "rtt_avg": 0.099, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.086, 104 | "rtt_max": 0.291, 105 | "rtt_avg": 0.101, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "chacha20": { 110 | "iperf": { 111 | "throughput": 2854407000.0, 112 | "cpu_sender": 5.648368, 113 | "cpu_receiver": 58.473016 114 | }, 115 | "ping_100": { 116 | "rtt_min": 0.082, 117 | "rtt_max": 0.515, 118 | "rtt_avg": 0.098, 119 | "pkt_loss": 0.0 120 | }, 121 | "ping_500": { 122 | "rtt_min": 0.089, 123 | "rtt_max": 3.457, 124 | "rtt_avg": 0.105, 125 | "pkt_loss": 0.0 126 | }, 127 | "ping_1000": { 128 | "rtt_min": 0.092, 129 | "rtt_max": 0.366, 130 | "rtt_avg": 0.108, 131 | "pkt_loss": 0.0 132 | } 133 | }, 134 | "results": { 135 | "throughput_mbits": { 136 | "native": 9681.224, 137 | "plain": 5472.962, 138 | "aes256": 3947.676, 139 | "aes128": 4200.596, 140 | "chacha20": 2854.407 141 | }, 142 | "latency_us": { 143 | "plain": { 144 | "100": 21.0, 145 | "500": 21.5, 146 | "1000": 20.0 147 | }, 148 | "aes256": { 149 | "100": 22.500000000000004, 150 | "500": 22.000000000000004, 151 | "1000": 21.5 152 | }, 153 | "aes128": { 154 | "100": 23.000000000000004, 155 | "500": 22.500000000000004, 156 | "1000": 22.500000000000004 157 | }, 158 | "chacha20": { 159 | "100": 23.500000000000004, 160 | "500": 25.5, 161 | "1000": 26.0 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2021-02-06_2.1.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0a6dc7529cd559185", 6 | "version": "2.1.0", 7 | "duration": 622.053159236908 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9672965000.0, 12 | "cpu_sender": 11.936759, 13 | "cpu_receiver": 70.348812 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.046, 17 | "rtt_max": 0.246, 18 | "rtt_avg": 0.053, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.048, 23 | "rtt_max": 0.183, 24 | "rtt_avg": 0.055, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.05, 29 | "rtt_max": 0.272, 30 | "rtt_avg": 0.057, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 5728527000.0, 37 | "cpu_sender": 11.004746, 38 | "cpu_receiver": 67.527328 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.078, 42 | "rtt_max": 0.372, 43 | "rtt_avg": 0.095, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.078, 48 | "rtt_max": 0.272, 49 | "rtt_avg": 0.094, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.082, 54 | "rtt_max": 0.217, 55 | "rtt_avg": 0.096, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3706944000.0, 62 | "cpu_sender": 6.465523, 63 | "cpu_receiver": 60.216674 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.079, 67 | "rtt_max": 0.28, 68 | "rtt_avg": 0.097, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.079, 73 | "rtt_max": 13.372, 74 | "rtt_avg": 0.099, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.086, 79 | "rtt_max": 0.358, 80 | "rtt_avg": 0.102, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "aes128": { 85 | "iperf": { 86 | "throughput": 3876646000.0, 87 | "cpu_sender": 6.800352, 88 | "cpu_receiver": 61.738244 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.078, 92 | "rtt_max": 0.219, 93 | "rtt_avg": 0.096, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.083, 98 | "rtt_max": 0.232, 99 | "rtt_avg": 0.097, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.087, 104 | "rtt_max": 0.327, 105 | "rtt_avg": 0.099, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "chacha20": { 110 | "iperf": { 111 | "throughput": 2917879000.0, 112 | "cpu_sender": 5.066722, 113 | "cpu_receiver": 55.171241 114 | }, 115 | "ping_100": { 116 | "rtt_min": 0.081, 117 | "rtt_max": 0.283, 118 | "rtt_avg": 0.097, 119 | "pkt_loss": 0.0 120 | }, 121 | "ping_500": { 122 | "rtt_min": 0.087, 123 | "rtt_max": 0.348, 124 | "rtt_avg": 0.103, 125 | "pkt_loss": 0.0 126 | }, 127 | "ping_1000": { 128 | "rtt_min": 0.088, 129 | "rtt_max": 0.309, 130 | "rtt_avg": 0.105, 131 | "pkt_loss": 0.0 132 | } 133 | }, 134 | "results": { 135 | "throughput_mbits": { 136 | "native": 9672.965, 137 | "plain": 5728.527, 138 | "aes256": 3706.944, 139 | "aes128": 3876.646, 140 | "chacha20": 2917.879 141 | }, 142 | "latency_us": { 143 | "plain": { 144 | "100": 21.0, 145 | "500": 19.5, 146 | "1000": 19.5 147 | }, 148 | "aes256": { 149 | "100": 22.000000000000004, 150 | "500": 22.000000000000004, 151 | "1000": 22.499999999999996 152 | }, 153 | "aes128": { 154 | "100": 21.5, 155 | "500": 21.0, 156 | "1000": 21.0 157 | }, 158 | "chacha20": { 159 | "100": 22.000000000000004, 160 | "500": 23.999999999999996, 161 | "1000": 23.999999999999996 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2021-04-06_2.2.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-0db9040eb3ab74509", 6 | "version": "2.2.0", 7 | "duration": 623.0307722091675 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9680235000.0, 12 | "cpu_sender": 12.015535, 13 | "cpu_receiver": 71.982452 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.049, 17 | "rtt_max": 0.219, 18 | "rtt_avg": 0.058, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.053, 23 | "rtt_max": 0.247, 24 | "rtt_avg": 0.059, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.053, 29 | "rtt_max": 0.189, 30 | "rtt_avg": 0.06, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 5790600000.0, 37 | "cpu_sender": 14.109763, 38 | "cpu_receiver": 69.727033 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.079, 42 | "rtt_max": 0.291, 43 | "rtt_avg": 0.094, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.079, 48 | "rtt_max": 0.304, 49 | "rtt_avg": 0.096, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.082, 54 | "rtt_max": 0.367, 55 | "rtt_avg": 0.097, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3917767000.0, 62 | "cpu_sender": 6.439156, 63 | "cpu_receiver": 64.267206 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.081, 67 | "rtt_max": 0.206, 68 | "rtt_avg": 0.097, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.088, 73 | "rtt_max": 0.206, 74 | "rtt_avg": 0.1, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.089, 79 | "rtt_max": 0.319, 80 | "rtt_avg": 0.103, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "aes128": { 85 | "iperf": { 86 | "throughput": 3697142000.0, 87 | "cpu_sender": 7.417808, 88 | "cpu_receiver": 59.433831 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.083, 92 | "rtt_max": 0.265, 93 | "rtt_avg": 0.097, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.081, 98 | "rtt_max": 0.369, 99 | "rtt_avg": 0.102, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.086, 104 | "rtt_max": 0.448, 105 | "rtt_avg": 0.102, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "chacha20": { 110 | "iperf": { 111 | "throughput": 3194412000.0, 112 | "cpu_sender": 6.12856, 113 | "cpu_receiver": 61.223349 114 | }, 115 | "ping_100": { 116 | "rtt_min": 0.081, 117 | "rtt_max": 0.28, 118 | "rtt_avg": 0.098, 119 | "pkt_loss": 0.0 120 | }, 121 | "ping_500": { 122 | "rtt_min": 0.088, 123 | "rtt_max": 0.264, 124 | "rtt_avg": 0.103, 125 | "pkt_loss": 0.0 126 | }, 127 | "ping_1000": { 128 | "rtt_min": 0.092, 129 | "rtt_max": 0.204, 130 | "rtt_avg": 0.106, 131 | "pkt_loss": 0.0 132 | } 133 | }, 134 | "results": { 135 | "throughput_mbits": { 136 | "native": 9680.235, 137 | "plain": 5790.6, 138 | "aes256": 3917.767, 139 | "aes128": 3697.142, 140 | "chacha20": 3194.412 141 | }, 142 | "latency_us": { 143 | "plain": { 144 | "100": 18.0, 145 | "500": 18.500000000000004, 146 | "1000": 18.500000000000004 147 | }, 148 | "aes256": { 149 | "100": 19.5, 150 | "500": 20.500000000000004, 151 | "1000": 21.5 152 | }, 153 | "aes128": { 154 | "100": 19.5, 155 | "500": 21.5, 156 | "1000": 20.999999999999996 157 | }, 158 | "chacha20": { 159 | "100": 20.0, 160 | "500": 22.0, 161 | "1000": 23.0 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /contrib/aws/measurements/2021-12-23_2.3.0_perf.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "region": "eu-central-1", 4 | "instance_type": "m5.large", 5 | "ami": "ami-099ccc441b2ef41ec", 6 | "version": "2.3.0", 7 | "duration": 622.5463161468506 8 | }, 9 | "native": { 10 | "iperf": { 11 | "throughput": 9529265000.0, 12 | "cpu_sender": 11.32918, 13 | "cpu_receiver": 61.870429 14 | }, 15 | "ping_100": { 16 | "rtt_min": 0.046, 17 | "rtt_max": 0.225, 18 | "rtt_avg": 0.053, 19 | "pkt_loss": 0.0 20 | }, 21 | "ping_500": { 22 | "rtt_min": 0.047, 23 | "rtt_max": 10.98, 24 | "rtt_avg": 0.054, 25 | "pkt_loss": 0.0 26 | }, 27 | "ping_1000": { 28 | "rtt_min": 0.048, 29 | "rtt_max": 0.175, 30 | "rtt_avg": 0.056, 31 | "pkt_loss": 0.0 32 | } 33 | }, 34 | "plain": { 35 | "iperf": { 36 | "throughput": 6388312000.0, 37 | "cpu_sender": 16.955082, 38 | "cpu_receiver": 72.705695 39 | }, 40 | "ping_100": { 41 | "rtt_min": 0.076, 42 | "rtt_max": 11.973, 43 | "rtt_avg": 0.09, 44 | "pkt_loss": 0.0 45 | }, 46 | "ping_500": { 47 | "rtt_min": 0.08, 48 | "rtt_max": 10.95, 49 | "rtt_avg": 0.094, 50 | "pkt_loss": 0.0 51 | }, 52 | "ping_1000": { 53 | "rtt_min": 0.081, 54 | "rtt_max": 1.638, 55 | "rtt_avg": 0.095, 56 | "pkt_loss": 0.0 57 | } 58 | }, 59 | "aes256": { 60 | "iperf": { 61 | "throughput": 3801851000.0, 62 | "cpu_sender": 5.826756, 63 | "cpu_receiver": 61.612033 64 | }, 65 | "ping_100": { 66 | "rtt_min": 0.075, 67 | "rtt_max": 0.9, 68 | "rtt_avg": 0.093, 69 | "pkt_loss": 0.0 70 | }, 71 | "ping_500": { 72 | "rtt_min": 0.079, 73 | "rtt_max": 0.275, 74 | "rtt_avg": 0.091, 75 | "pkt_loss": 0.0 76 | }, 77 | "ping_1000": { 78 | "rtt_min": 0.08, 79 | "rtt_max": 1.015, 80 | "rtt_avg": 0.093, 81 | "pkt_loss": 0.0 82 | } 83 | }, 84 | "aes128": { 85 | "iperf": { 86 | "throughput": 3880325000.0, 87 | "cpu_sender": 6.219277, 88 | "cpu_receiver": 62.125445 89 | }, 90 | "ping_100": { 91 | "rtt_min": 0.077, 92 | "rtt_max": 11.656, 93 | "rtt_avg": 0.09, 94 | "pkt_loss": 0.0 95 | }, 96 | "ping_500": { 97 | "rtt_min": 0.08, 98 | "rtt_max": 0.211, 99 | "rtt_avg": 0.095, 100 | "pkt_loss": 0.0 101 | }, 102 | "ping_1000": { 103 | "rtt_min": 0.082, 104 | "rtt_max": 1.398, 105 | "rtt_avg": 0.095, 106 | "pkt_loss": 0.0 107 | } 108 | }, 109 | "chacha20": { 110 | "iperf": { 111 | "throughput": 3126447000.0, 112 | "cpu_sender": 5.113819, 113 | "cpu_receiver": 58.58095 114 | }, 115 | "ping_100": { 116 | "rtt_min": 0.079, 117 | "rtt_max": 0.271, 118 | "rtt_avg": 0.091, 119 | "pkt_loss": 0.0 120 | }, 121 | "ping_500": { 122 | "rtt_min": 0.083, 123 | "rtt_max": 0.272, 124 | "rtt_avg": 0.098, 125 | "pkt_loss": 0.0 126 | }, 127 | "ping_1000": { 128 | "rtt_min": 0.087, 129 | "rtt_max": 1.615, 130 | "rtt_avg": 0.101, 131 | "pkt_loss": 0.0 132 | } 133 | }, 134 | "results": { 135 | "throughput_mbits": { 136 | "native": 9529.265, 137 | "plain": 6388.312, 138 | "aes256": 3801.851, 139 | "aes128": 3880.325, 140 | "chacha20": 3126.447 141 | }, 142 | "latency_us": { 143 | "plain": { 144 | "100": 18.5, 145 | "500": 20.0, 146 | "1000": 19.5 147 | }, 148 | "aes256": { 149 | "100": 20.0, 150 | "500": 18.5, 151 | "1000": 18.5 152 | }, 153 | "aes128": { 154 | "100": 18.5, 155 | "500": 20.5, 156 | "1000": 19.5 157 | }, 158 | "chacha20": { 159 | "100": 19.0, 160 | "500": 22.000000000000004, 161 | "1000": 22.500000000000004 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /contrib/aws/performance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from common import EC2Environment, CREATE, eprint 4 | import time, json 5 | from datetime import date 6 | 7 | 8 | # Note: this script will run for ~8 minutes and incur costs of about $ 0.02 9 | 10 | FILE = "../../target/release/vpncloud" 11 | VERSION = "2.4.0" 12 | REGION = "eu-central-1" 13 | 14 | env = EC2Environment( 15 | region = REGION, 16 | node_count = 2, 17 | instance_type = "m5.large", 18 | use_spot = False, 19 | max_price = "0.08", # USD per hour per VM 20 | vpncloud_version = VERSION, 21 | vpncloud_file = FILE, 22 | cluster_nodes = True, 23 | subnet = CREATE, 24 | keyname = CREATE 25 | ) 26 | 27 | 28 | CRYPTO = ["plain", "aes256", "aes128", "chacha20"] 29 | 30 | 31 | class PerfTest: 32 | def __init__(self, sender, receiver, meta): 33 | self.sender = sender 34 | self.receiver = receiver 35 | self.sender_ip_vpncloud = "10.0.0.1" 36 | self.receiver_ip_vpncloud = "10.0.0.2" 37 | self.meta = meta 38 | 39 | @classmethod 40 | def from_ec2_env(cls, env): 41 | meta = { 42 | "region": env.region, 43 | "instance_type": env.instance_type, 44 | "ami": env.ami, 45 | "version": env.vpncloud_version 46 | } 47 | return cls(env.nodes[0], env.nodes[1], meta) 48 | 49 | def run_ping(self, dst, size): 50 | eprint("\tRunning ping {} with size {} ...".format(dst, size)) 51 | return self.sender.ping(dst=dst, size=size, count=30000, interval=0.001) 52 | 53 | def run_iperf(self, dst): 54 | eprint("\tRunning iperf on {} ...".format(dst)) 55 | self.receiver.start_iperf_server() 56 | time.sleep(0.1) 57 | result = self.sender.run_iperf(dst=dst, duration=30) 58 | self.receiver.stop_iperf_server() 59 | return result 60 | 61 | def run_suite(self, dst): 62 | return { 63 | "iperf": self.run_iperf(dst), 64 | "ping_100": self.run_ping(dst, 100), 65 | "ping_500": self.run_ping(dst, 500), 66 | "ping_1000": self.run_ping(dst, 1000), 67 | } 68 | 69 | def start_vpncloud(self, crypto=None): 70 | eprint("\tSetting up vpncloud on receiver") 71 | self.receiver.start_vpncloud(crypto=crypto, ip="{}/24".format(self.receiver_ip_vpncloud)) 72 | eprint("\tSetting up vpncloud on sender") 73 | self.sender.start_vpncloud(crypto=crypto, peers=["{}:3210".format(self.receiver.private_ip)], ip="{}/24".format(self.sender_ip_vpncloud)) 74 | time.sleep(1.0) 75 | 76 | def stop_vpncloud(self): 77 | self.sender.stop_vpncloud(wait=False) 78 | self.receiver.stop_vpncloud(wait=True) 79 | 80 | def run(self): 81 | eprint("Testing native network") 82 | results = { 83 | "meta": self.meta, 84 | "native": self.run_suite(self.receiver.private_ip) 85 | } 86 | for crypto in CRYPTO: 87 | eprint("Running with crypto {}".format(crypto)) 88 | self.start_vpncloud(crypto=crypto) 89 | res = self.run_suite(self.receiver_ip_vpncloud) 90 | self.stop_vpncloud() 91 | results[str(crypto)] = res 92 | results['results'] = { 93 | "throughput_mbits": dict([ 94 | (k, results[k]["iperf"]["throughput"] / 1000000.0) for k in ["native"] + CRYPTO 95 | ]), 96 | "latency_us": dict([ 97 | (k, dict([ 98 | (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] 99 | ])) for k in CRYPTO 100 | ]) 101 | } 102 | return results 103 | 104 | perf = PerfTest.from_ec2_env(env) 105 | 106 | start = time.time() 107 | results = perf.run() 108 | duration = time.time() - start 109 | 110 | results["meta"]["duration"] = duration 111 | 112 | name = "measurements/{date}_{version}_perf.json".format(date=date.today().strftime('%Y-%m-%d'), version=VERSION) 113 | eprint('Storing results in {}'.format(name)) 114 | with open(name, 'w') as fp: 115 | json.dump(results, fp, indent=2) 116 | eprint("done.") 117 | -------------------------------------------------------------------------------- /contrib/aws/testnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from common import EC2Environment, CREATE 4 | import atexit, argparse, os 5 | 6 | REGION = "eu-central-1" 7 | 8 | VERSION = "2.1.0" 9 | 10 | 11 | parser = argparse.ArgumentParser(description='Create a test setup') 12 | parser.add_argument('--instancetype', default='t3a.nano', help='EC2 instance type') 13 | parser.add_argument('--version', default=VERSION, help='VpnCloud version to use') 14 | parser.add_argument('--count', '-c', dest="count", type=int, default=2, help='Number of instance to create') 15 | parser.add_argument('--cluster', action="store_true", help='Cluster instances to get reliable throughput') 16 | parser.add_argument('--subnet', help='AWS subnet id to use (empty = create new one)') 17 | parser.add_argument('--keyname', help='Name of AWS keypair to use (empty = create new one)') 18 | parser.add_argument('--keyfile', default="key.pem", help='Path of the private key file') 19 | 20 | 21 | args = parser.parse_args() 22 | 23 | privatekey = None 24 | if args.keyname: 25 | with open(args.keyfile, 'r') as fp: 26 | privatekey = fp.read() 27 | 28 | opts = {} 29 | if os.path.exists(args.version): 30 | opts["vpncloud_file"] = args.version 31 | opts["vpncloud_version"] = None 32 | else: 33 | opts["vpncloud_version"] = args.version 34 | 35 | setup = EC2Environment( 36 | region = REGION, 37 | node_count = args.count, 38 | instance_type = args.instancetype, 39 | cluster_nodes = args.cluster, 40 | subnet = args.subnet or CREATE, 41 | keyname = args.keyname or CREATE, 42 | privatekey = privatekey, 43 | **opts 44 | ) 45 | 46 | if not args.keyname: 47 | assert not os.path.exists(args.keyfile) 48 | with open(args.keyfile, 'x') as fp: 49 | fp.write(setup.privatekey) 50 | os.chmod(args.keyfile, 0o400) 51 | print("SSH private key written to {}".format(args.keyfile)) 52 | atexit.register(lambda : os.remove(args.keyfile)) 53 | print() 54 | 55 | print("Nodes:") 56 | for node in setup.nodes: 57 | print("\t {}@{}\tprivate: {}".format(setup.username, node.public_ip, node.private_ip)) 58 | print() 59 | 60 | print("Press ENTER to shut down") 61 | input() -------------------------------------------------------------------------------- /maskfile.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | Needs [mask](https://github.com/jacobdeichert/mask) to run. 4 | 5 | 6 | ## install-tools 7 | 8 | > Install tools. 9 | 10 | ```sh 11 | set -e 12 | apt-get install -y asciidoctor 13 | cargo install cargo-binstall 14 | cargo binstall cross cargo-deb cargo-generate-rpm 15 | UPX_VERSION=$(grep -e '^upx_version =' Cargo.toml | sed -e 's/upx_version = "\(.*\)"/\1/') 16 | 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 17 | ``` 18 | 19 | ## manpage 20 | 21 | > Generate manpage. 22 | 23 | ```sh 24 | set -e 25 | echo >&2 "Generating manpage" 26 | if [ ! -f target/vpncloud.1.gz -o vpncloud.adoc -nt target/vpncloud.1.gz ]; then 27 | asciidoctor -b manpage -o target/vpncloud.1 vpncloud.adoc 28 | gzip -f target/vpncloud.1 29 | fi 30 | ``` 31 | 32 | ## build-packages-cross (target) (target_name) (target_name_rpm) 33 | 34 | > Build the project packages for a given target. 35 | 36 | ```sh 37 | set -e 38 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 39 | TARGET=$target 40 | TARGET_DIR=target/$target_name 41 | 42 | # compile 43 | echo >&2 "Compiling for $target_name" 44 | cross build --release --target $TARGET --target-dir $TARGET_DIR 45 | mkdir -p target/$TARGET/release 46 | cp $TARGET_DIR/$TARGET/release/vpncloud target/$TARGET/release/ 47 | 48 | # build deb 49 | echo >&2 "Building deb package" 50 | cargo deb --no-build --no-strip --target $TARGET 51 | mv target/$TARGET/debian/vpncloud_${VERSION}-1_$target_name.deb dist/vpncloud_${VERSION}_$target_name.deb 52 | 53 | # build rpm 54 | if [ -n "$target_name_rpm" ]; then 55 | echo >&2 "Building rpm package" 56 | cargo generate-rpm --target $TARGET --target-dir $TARGET_DIR 57 | mv $TARGET_DIR/$TARGET/generate-rpm/vpncloud-${VERSION}-1.$target_name_rpm.rpm dist/vpncloud_${VERSION}-1.$target_name_rpm.rpm 58 | fi 59 | ``` 60 | 61 | ## build-amd64-packages 62 | 63 | ```sh 64 | $MASK build-packages-cross x86_64-unknown-linux-gnu amd64 x86_64 65 | ``` 66 | 67 | ## build-i386-packages 68 | 69 | ```sh 70 | $MASK build-packages-cross i686-unknown-linux-gnu i386 i686 71 | ``` 72 | 73 | ## build-arm64-packages 74 | 75 | ```sh 76 | $MASK build-packages-cross aarch64-unknown-linux-gnu arm64 aarch64 77 | ``` 78 | 79 | ## build-armhf-packages 80 | 81 | ```sh 82 | $MASK build-packages-cross armv7-unknown-linux-gnueabihf armhf "" 83 | ``` 84 | 85 | ## build-armel-packages 86 | 87 | ```sh 88 | $MASK build-packages-cross armv5te-unknown-linux-gnueabi armel "" 89 | ``` 90 | 91 | 92 | 93 | ## build-static-cross (target) (target_name) 94 | 95 | > Build the project statically for a given target. 96 | 97 | ```sh 98 | set -e 99 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 100 | TARGET=$target 101 | TARGET_DIR=target/$target_name-musl 102 | BIN=$TARGET_DIR/$TARGET/release/vpncloud 103 | 104 | echo >&2 "Compiling for $target_name musl" 105 | cross build --release --features installer --target $TARGET --target-dir $TARGET_DIR 106 | upx --lzma $BIN 107 | cp $BIN dist/vpncloud_${VERSION}_static_$target_name 108 | ``` 109 | 110 | ## build-amd64-static 111 | 112 | ```sh 113 | $MASK build-static-cross x86_64-unknown-linux-musl amd64 114 | ``` 115 | 116 | 117 | ## build-i386-static 118 | 119 | ```sh 120 | $MASK build-static-cross i686-unknown-linux-musl i386 121 | ``` 122 | 123 | 124 | ## build-arm64-static 125 | 126 | ```sh 127 | $MASK build-static-cross aarch64-unknown-linux-musl arm64 128 | ``` 129 | 130 | ## build-armhf-static 131 | 132 | ```sh 133 | $MASK build-static-cross armv7-unknown-linux-musleabihf armhf 134 | ``` 135 | 136 | ## build-armel-static 137 | 138 | ```sh 139 | $MASK build-static-cross armv5te-unknown-linux-musleabi armel 140 | ``` 141 | 142 | 143 | ## build 144 | 145 | > Build the project for all architectures. 146 | 147 | ```sh 148 | set -e 149 | $MASK manpage 150 | $MASK build-amd64-packages 151 | $MASK build-amd64-static 152 | $MASK build-i386-packages 153 | $MASK build-i386-static 154 | $MASK build-arm64-packages 155 | $MASK build-arm64-static 156 | $MASK build-armhf-packages 157 | $MASK build-armhf-static 158 | $MASK build-armel-packages 159 | $MASK build-armel-static 160 | ``` 161 | 162 | ## sign 163 | 164 | > Sign the packages. 165 | 166 | ```sh 167 | set -e 168 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 169 | cd dist 170 | sha256sum vpncloud_${VERSION}_static_* vpncloud_${VERSION}*.rpm vpncloud_${VERSION}*.deb > vpncloud_${VERSION}_SHA256SUMS.txt 171 | gpg --armor --output vpncloud_${VERSION}_SHA256SUMS.txt.asc --detach-sig vpncloud_${VERSION}_SHA256SUMS.txt 172 | ``` 173 | 174 | ## test 175 | 176 | > Test the project. 177 | 178 | ```sh 179 | cargo test --all-features 180 | ``` 181 | 182 | ## release 183 | 184 | > Release the project. 185 | 186 | ```sh 187 | set -e 188 | 189 | $MASK test 190 | nano Cargo.toml 191 | VERSION=$(grep -e '^version =' Cargo.toml | sed -e 's/version = "\(.*\)"/\1/') 192 | nano CHANGELOG.md 193 | nano assets/changelog.txt 194 | $MASK build 195 | $MASK sign 196 | git commit -a 197 | cargo publish 198 | git tag v$VERSION 199 | git push --tags 200 | ``` 201 | 202 | 203 | ## count 204 | 205 | > Count the lines of code. 206 | 207 | ```sh 208 | tokei 209 | ``` -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | comment_width = 120 3 | fn_args_layout = "Compressed" 4 | where_single_line = true 5 | merge_imports = true 6 | max_width = 120 7 | force_multiline_blocks = true 8 | normalize_comments = true 9 | trailing_comma = "Never" 10 | trailing_semicolon = false 11 | use_field_init_shorthand = true 12 | use_try_shorthand = true 13 | wrap_comments = true 14 | overflow_delimited_expr = true 15 | blank_lines_upper_bound = 2 16 | normalize_doc_attributes = true 17 | inline_attribute_width = 50 18 | edition = "2018" 19 | reorder_impl_items = true -------------------------------------------------------------------------------- /src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | mod common; 6 | mod core; 7 | mod init; 8 | mod rotate; 9 | 10 | pub use self::core::{EXTRA_LEN, TAG_LEN}; 11 | pub use common::*; 12 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | use thiserror::Error; 6 | 7 | use std::io; 8 | 9 | #[derive(Error, Debug)] 10 | pub enum Error { 11 | /// Crypto init error, this is recoverable 12 | #[error("Crypto initialization error: {0}")] 13 | CryptoInit(&'static str), 14 | 15 | /// Crypto init error, this is fatal and the init needs to be aborted 16 | #[error("Fatal crypto initialization error: {0}")] 17 | CryptoInitFatal(&'static str), 18 | 19 | /// Crypto error with this one message, no permanent error 20 | #[error("Crypto error: {0}")] 21 | Crypto(&'static str), 22 | 23 | #[error("Invalid crypto state: {0}")] 24 | InvalidCryptoState(&'static str), 25 | 26 | #[error("Invalid config: {0}")] 27 | InvalidConfig(&'static str), 28 | 29 | #[error("Socker error: {0}")] 30 | Socket(&'static str), 31 | 32 | #[error("Socker error: {0} ({1})")] 33 | SocketIo(&'static str, #[source] io::Error), 34 | 35 | #[error("Device error: {0}")] 36 | Device(&'static str), 37 | 38 | #[error("Device error: {0} ({1})")] 39 | DeviceIo(&'static str, #[source] io::Error), 40 | 41 | #[error("File error: {0}")] 42 | FileIo(&'static str, #[source] io::Error), 43 | 44 | #[error("Message error: {0}")] 45 | Message(&'static str), 46 | 47 | #[error("Beacon error: {0} ({1})")] 48 | BeaconIo(&'static str, #[source] io::Error), 49 | 50 | #[error("Parse error: {0}")] 51 | Parse(&'static str), 52 | 53 | #[error("Name can not be resolved: {0}")] 54 | NameUnresolvable(String), 55 | } 56 | -------------------------------------------------------------------------------- /src/installer.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Error, util::run_cmd}; 2 | use std::{ 3 | env, 4 | fs::{self, File}, 5 | io::Write, 6 | os::unix::fs::PermissionsExt, 7 | process::Command, 8 | }; 9 | 10 | const MANPAGE: &[u8] = include_bytes!("../target/vpncloud.1.gz"); 11 | const SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud@.service"); 12 | const TARGET_FILE: &[u8] = include_bytes!("../assets/vpncloud.target"); 13 | const WS_PROXY_SERVICE_FILE: &[u8] = include_bytes!("../assets/vpncloud-wsproxy.service"); 14 | const EXAMPLE_CONFIG: &[u8] = include_bytes!("../assets/example.net.disabled"); 15 | 16 | fn systemctl_daemon_reload() { 17 | let mut cmd = Command::new("systemctl"); 18 | cmd.arg("daemon-reload"); 19 | run_cmd(cmd); 20 | } 21 | 22 | pub fn install() -> Result<(), Error> { 23 | env::current_exe() 24 | .and_then(|p| fs::copy(p, "/usr/bin/vpncloud")) 25 | .map_err(|e| Error::FileIo("Failed to copy binary", e))?; 26 | fs::set_permissions("/usr/bin/vpncloud", fs::Permissions::from_mode(0o755)) 27 | .map_err(|e| Error::FileIo("Failed to set permissions for binary", e))?; 28 | fs::create_dir_all("/etc/vpncloud").map_err(|e| Error::FileIo("Failed to create config folder", e))?; 29 | fs::set_permissions("/etc/vpncloud", fs::Permissions::from_mode(0o700)) 30 | .map_err(|e| Error::FileIo("Failed to set permissions for config folder", e))?; 31 | File::create("/etc/vpncloud/example.net.disabled") 32 | .and_then(|mut f| f.write_all(EXAMPLE_CONFIG)) 33 | .map_err(|e| Error::FileIo("Failed to create example config", e))?; 34 | File::create("/usr/share/man/man1/vpncloud.1.gz") 35 | .and_then(|mut f| f.write_all(MANPAGE)) 36 | .map_err(|e| Error::FileIo("Failed to create manpage", e))?; 37 | File::create("/lib/systemd/system/vpncloud@.service") 38 | .and_then(|mut f| f.write_all(SERVICE_FILE)) 39 | .map_err(|e| Error::FileIo("Failed to create service file", e))?; 40 | File::create("/lib/systemd/system/vpncloud.target") 41 | .and_then(|mut f| f.write_all(TARGET_FILE)) 42 | .map_err(|e| Error::FileIo("Failed to create service target file", e))?; 43 | File::create("/lib/systemd/system/vpncloud-wsproxy.service") 44 | .and_then(|mut f| f.write_all(WS_PROXY_SERVICE_FILE)) 45 | .map_err(|e| Error::FileIo("Failed to create wsporxy service file", e))?; 46 | systemctl_daemon_reload(); 47 | info!("Install successful"); 48 | Ok(()) 49 | } 50 | 51 | pub fn uninstall() -> Result<(), Error> { 52 | fs::remove_file("/etc/vpncloud/example.net.disabled").map_err(|e| Error::FileIo("Failed to remove binary", e))?; 53 | fs::remove_file("/usr/share/man/man1/vpncloud.1.gz").map_err(|e| Error::FileIo("Failed to remove manpage", e))?; 54 | fs::remove_file("/lib/systemd/system/vpncloud@.service") 55 | .map_err(|e| Error::FileIo("Failed to remove service file", e))?; 56 | fs::remove_file("/lib/systemd/system/vpncloud.target") 57 | .map_err(|e| Error::FileIo("Failed to remove service target file", e))?; 58 | fs::remove_file("/lib/systemd/system/vpncloud-wsproxy.service") 59 | .map_err(|e| Error::FileIo("Failed to remove wsproxy service file", e))?; 60 | fs::remove_file("/usr/bin/vpncloud").map_err(|e| Error::FileIo("Failed to remove binary", e))?; 61 | systemctl_daemon_reload(); 62 | info!("Uninstall successful"); 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | use crate::{ 6 | crypto::Payload, 7 | error::Error, 8 | types::{NodeId, Range, RangeList, NODE_ID_BYTES}, 9 | util::MsgBuffer, 10 | }; 11 | use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; 12 | use smallvec::{smallvec, SmallVec}; 13 | use std::{ 14 | io::{self, Cursor, Read, Seek, SeekFrom, Take, Write}, 15 | net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, 16 | }; 17 | 18 | pub const MESSAGE_TYPE_DATA: u8 = 0; 19 | pub const MESSAGE_TYPE_NODE_INFO: u8 = 1; 20 | pub const MESSAGE_TYPE_KEEPALIVE: u8 = 2; 21 | pub const MESSAGE_TYPE_CLOSE: u8 = 0xff; 22 | 23 | pub type AddrList = SmallVec<[SocketAddr; 4]>; 24 | pub type PeerList = SmallVec<[PeerInfo; 16]>; 25 | 26 | #[derive(Debug, PartialEq)] 27 | pub struct PeerInfo { 28 | pub node_id: Option, 29 | pub addrs: AddrList, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | pub struct NodeInfo { 34 | pub node_id: NodeId, 35 | pub peers: PeerList, 36 | pub claims: RangeList, 37 | pub peer_timeout: Option, 38 | pub addrs: AddrList, 39 | } 40 | 41 | impl NodeInfo { 42 | const PART_CLAIMS: u8 = 2; 43 | const PART_END: u8 = 0; 44 | const PART_NODEID: u8 = 4; 45 | const PART_PEERS: u8 = 1; 46 | const PART_PEER_TIMEOUT: u8 = 3; 47 | const PART_ADDRS: u8 = 5; 48 | 49 | fn read_addr_list(r: &mut Take) -> Result { 50 | let flags = r.read_u8()?; 51 | Self::read_addr_list_inner(r, flags) 52 | } 53 | 54 | fn read_addr_list_inner(r: &mut Take, flags: u8) -> Result { 55 | let num_ipv4_addrs = (flags & 0x07) as usize; 56 | let num_ipv6_addrs = (flags & 0x38) as usize / 8; 57 | let mut addrs = SmallVec::with_capacity(num_ipv4_addrs + num_ipv6_addrs); 58 | for _ in 0..num_ipv6_addrs { 59 | let mut ip = [0u8; 16]; 60 | r.read_exact(&mut ip)?; 61 | let port = r.read_u16::()?; 62 | let addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0)); 63 | addrs.push(addr); 64 | } 65 | for _ in 0..num_ipv4_addrs { 66 | let mut ip = [0u8; 4]; 67 | r.read_exact(&mut ip)?; 68 | let port = r.read_u16::()?; 69 | let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(ip), port)); 70 | addrs.push(addr); 71 | } 72 | Ok(addrs) 73 | } 74 | 75 | fn decode_peer_list_part(r: &mut Take) -> Result { 76 | let mut peers = smallvec![]; 77 | while r.limit() > 0 { 78 | let flags = r.read_u8()?; 79 | let has_node_id = (flags & 0x80) != 0; 80 | let mut node_id = None; 81 | if has_node_id { 82 | let mut id = [0; NODE_ID_BYTES]; 83 | r.read_exact(&mut id)?; 84 | node_id = Some(id) 85 | } 86 | let addrs = Self::read_addr_list_inner(r, flags)?; 87 | peers.push(PeerInfo { addrs, node_id }) 88 | } 89 | Ok(peers) 90 | } 91 | 92 | fn decode_claims_part(mut r: &mut Take) -> Result { 93 | let mut claims = smallvec![]; 94 | while r.limit() > 0 { 95 | claims.push(Range::read_from(&mut r)?); 96 | } 97 | Ok(claims) 98 | } 99 | 100 | fn decode_internal(mut r: R) -> Result { 101 | let mut peers = smallvec![]; 102 | let mut claims = smallvec![]; 103 | let mut peer_timeout = None; 104 | let mut node_id = None; 105 | let mut addrs = smallvec![]; 106 | loop { 107 | let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?; 108 | if part == Self::PART_END { 109 | break; 110 | } 111 | let part_len = r.read_u16::().map_err(|_| Error::Message("Truncated message"))? as usize; 112 | let mut rp = r.take(part_len as u64); 113 | match part { 114 | Self::PART_PEERS => { 115 | peers = Self::decode_peer_list_part(&mut rp).map_err(|_| Error::Message("Truncated message"))? 116 | } 117 | Self::PART_CLAIMS => claims = Self::decode_claims_part(&mut rp)?, 118 | Self::PART_PEER_TIMEOUT => { 119 | peer_timeout = 120 | Some(rp.read_u16::().map_err(|_| Error::Message("Truncated message"))?) 121 | } 122 | Self::PART_NODEID => { 123 | let mut data = [0; NODE_ID_BYTES]; 124 | rp.read_exact(&mut data).map_err(|_| Error::Message("Truncated message"))?; 125 | node_id = Some(data); 126 | } 127 | Self::PART_ADDRS => { 128 | addrs = Self::read_addr_list(&mut rp).map_err(|_| Error::Message("Truncated message"))?; 129 | } 130 | _ => { 131 | let mut data = vec![0; part_len]; 132 | rp.read_exact(&mut data).map_err(|_| Error::Message("Truncated message"))?; 133 | } 134 | } 135 | r = rp.into_inner(); 136 | } 137 | let node_id = match node_id { 138 | Some(node_id) => node_id, 139 | None => return Err(Error::Message("Payload without node_id")), 140 | }; 141 | Ok(Self { node_id, peers, claims, peer_timeout, addrs }) 142 | } 143 | 144 | pub fn decode(r: R) -> Result { 145 | Self::decode_internal(r).map_err(|_| Error::Message("Input data too short")) 146 | } 147 | 148 | fn encode_peer_list_part(&self, mut out: W) -> Result<(), io::Error> { 149 | for p in &self.peers { 150 | let mut addr_ipv4: SmallVec<[SocketAddrV4; 16]> = smallvec![]; 151 | let mut addr_ipv6: SmallVec<[SocketAddrV6; 16]> = smallvec![]; 152 | for a in &p.addrs { 153 | match a { 154 | SocketAddr::V4(addr) => addr_ipv4.push(*addr), 155 | SocketAddr::V6(addr) => addr_ipv6.push(*addr), 156 | } 157 | } 158 | while addr_ipv4.len() >= 8 { 159 | addr_ipv4.pop(); 160 | } 161 | while addr_ipv6.len() >= 8 { 162 | addr_ipv6.pop(); 163 | } 164 | let mut flags = addr_ipv6.len() as u8 * 8 + addr_ipv4.len() as u8; 165 | if p.node_id.is_some() { 166 | flags += 0x80; 167 | } 168 | out.write_u8(flags)?; 169 | if let Some(node_id) = &p.node_id { 170 | out.write_all(node_id)?; 171 | } 172 | for a in addr_ipv6 { 173 | out.write_all(&a.ip().octets())?; 174 | out.write_u16::(a.port())?; 175 | } 176 | for a in addr_ipv4 { 177 | out.write_all(&a.ip().octets())?; 178 | out.write_u16::(a.port())?; 179 | } 180 | } 181 | Ok(()) 182 | } 183 | 184 | fn encode_addrs_part(&self, mut out: W) -> Result<(), io::Error> { 185 | let mut addr_ipv4: SmallVec<[SocketAddrV4; 16]> = smallvec![]; 186 | let mut addr_ipv6: SmallVec<[SocketAddrV6; 16]> = smallvec![]; 187 | for a in &self.addrs { 188 | match a { 189 | SocketAddr::V4(addr) => addr_ipv4.push(*addr), 190 | SocketAddr::V6(addr) => addr_ipv6.push(*addr), 191 | } 192 | } 193 | while addr_ipv4.len() >= 8 { 194 | addr_ipv4.pop(); 195 | } 196 | while addr_ipv6.len() >= 8 { 197 | addr_ipv6.pop(); 198 | } 199 | let flags = addr_ipv6.len() as u8 * 8 + addr_ipv4.len() as u8; 200 | out.write_u8(flags)?; 201 | for a in addr_ipv6 { 202 | out.write_all(&a.ip().octets())?; 203 | out.write_u16::(a.port())?; 204 | } 205 | for a in addr_ipv4 { 206 | out.write_all(&a.ip().octets())?; 207 | out.write_u16::(a.port())?; 208 | } 209 | Ok(()) 210 | } 211 | 212 | fn encode_part) -> Result<(), io::Error>>( 213 | cursor: &mut Cursor<&mut [u8]>, part: u8, f: F, 214 | ) -> Result<(), io::Error> { 215 | cursor.write_u8(part)?; 216 | cursor.write_u16::(0)?; 217 | let part_start = cursor.position(); 218 | f(cursor)?; 219 | let part_end = cursor.position(); 220 | let len = part_end - part_start; 221 | cursor.seek(SeekFrom::Start(part_start - 2))?; 222 | cursor.write_u16::(len as u16)?; 223 | cursor.seek(SeekFrom::Start(part_end))?; 224 | Ok(()) 225 | } 226 | 227 | fn encode_internal(&self, buffer: &mut MsgBuffer) -> Result<(), io::Error> { 228 | let len; 229 | { 230 | let mut cursor = Cursor::new(buffer.buffer()); 231 | Self::encode_part(&mut cursor, Self::PART_NODEID, |cursor| cursor.write_all(&self.node_id))?; 232 | Self::encode_part(&mut cursor, Self::PART_PEERS, |cursor| self.encode_peer_list_part(cursor))?; 233 | Self::encode_part(&mut cursor, Self::PART_CLAIMS, |mut cursor| { 234 | for c in &self.claims { 235 | c.write_to(&mut cursor); 236 | } 237 | Ok(()) 238 | })?; 239 | if let Some(timeout) = self.peer_timeout { 240 | Self::encode_part(&mut cursor, Self::PART_PEER_TIMEOUT, |cursor| { 241 | cursor.write_u16::(timeout) 242 | })? 243 | } 244 | Self::encode_part(&mut cursor, Self::PART_ADDRS, |cursor| self.encode_addrs_part(cursor))?; 245 | cursor.write_u8(Self::PART_END)?; 246 | len = cursor.position() as usize; 247 | } 248 | buffer.set_length(len); 249 | Ok(()) 250 | } 251 | 252 | pub fn encode(&self, buffer: &mut MsgBuffer) { 253 | self.encode_internal(buffer).expect("Buffer too small") 254 | } 255 | } 256 | 257 | impl Payload for NodeInfo { 258 | fn write_to(&self, buffer: &mut MsgBuffer) { 259 | self.encode(buffer) 260 | } 261 | 262 | fn read_from(r: R) -> Result { 263 | Self::decode(r) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/net.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | use std::{ 6 | collections::{HashMap, VecDeque}, 7 | io::{self, ErrorKind}, 8 | net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket}, 9 | os::unix::io::{AsRawFd, RawFd}, 10 | sync::atomic::{AtomicBool, Ordering}, 11 | }; 12 | 13 | use super::util::{MockTimeSource, MsgBuffer, Time, TimeSource}; 14 | use crate::{config::DEFAULT_PORT, port_forwarding::PortForwarding}; 15 | 16 | pub fn mapped_addr(addr: SocketAddr) -> SocketAddr { 17 | // HOT PATH 18 | match addr { 19 | SocketAddr::V4(addr4) => SocketAddr::new(IpAddr::V6(addr4.ip().to_ipv6_mapped()), addr4.port()), 20 | _ => addr, 21 | } 22 | } 23 | 24 | pub fn get_ip() -> IpAddr { 25 | let s = UdpSocket::bind("0.0.0.0:0").unwrap(); 26 | s.connect("8.8.8.8:0").unwrap(); 27 | s.local_addr().unwrap().ip() 28 | } 29 | 30 | pub trait Socket: AsRawFd + Sized { 31 | fn listen(addr: &str) -> Result; 32 | fn receive(&mut self, buffer: &mut MsgBuffer) -> Result; 33 | fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result; 34 | fn address(&self) -> Result; 35 | fn create_port_forwarding(&self) -> Option; 36 | } 37 | 38 | pub fn parse_listen(addr: &str, default_port: u16) -> SocketAddr { 39 | if let Some(addr) = addr.strip_prefix("*:") { 40 | let port = try_fail!(addr.parse::(), "Invalid port: {}"); 41 | SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) 42 | } else if addr.contains(':') { 43 | try_fail!(addr.parse::(), "Invalid address: {}: {}", addr) 44 | } else if let Ok(port) = addr.parse::() { 45 | SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port) 46 | } else { 47 | let ip = try_fail!(addr.parse::(), "Invalid addr: {}"); 48 | SocketAddr::new(ip, default_port) 49 | } 50 | } 51 | 52 | impl Socket for UdpSocket { 53 | fn listen(addr: &str) -> Result { 54 | let addr = mapped_addr(parse_listen(addr, DEFAULT_PORT)); 55 | UdpSocket::bind(addr) 56 | } 57 | 58 | fn receive(&mut self, buffer: &mut MsgBuffer) -> Result { 59 | buffer.clear(); 60 | let (size, addr) = self.recv_from(buffer.buffer())?; 61 | buffer.set_length(size); 62 | Ok(addr) 63 | } 64 | 65 | fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result { 66 | self.send_to(data, addr) 67 | } 68 | 69 | fn address(&self) -> Result { 70 | let mut addr = self.local_addr()?; 71 | addr.set_ip(get_ip()); 72 | Ok(addr) 73 | } 74 | 75 | fn create_port_forwarding(&self) -> Option { 76 | PortForwarding::new(self.address().unwrap().port()) 77 | } 78 | } 79 | 80 | thread_local! { 81 | static MOCK_SOCKET_NAT: AtomicBool = AtomicBool::new(false); 82 | } 83 | 84 | pub struct MockSocket { 85 | nat: bool, 86 | nat_peers: HashMap, 87 | address: SocketAddr, 88 | outbound: VecDeque<(SocketAddr, Vec)>, 89 | inbound: VecDeque<(SocketAddr, Vec)>, 90 | } 91 | 92 | impl MockSocket { 93 | pub fn new(address: SocketAddr) -> Self { 94 | Self { 95 | nat: Self::get_nat(), 96 | nat_peers: HashMap::new(), 97 | address, 98 | outbound: VecDeque::with_capacity(10), 99 | inbound: VecDeque::with_capacity(10), 100 | } 101 | } 102 | 103 | pub fn set_nat(nat: bool) { 104 | MOCK_SOCKET_NAT.with(|t| t.store(nat, Ordering::SeqCst)) 105 | } 106 | 107 | pub fn get_nat() -> bool { 108 | MOCK_SOCKET_NAT.with(|t| t.load(Ordering::SeqCst)) 109 | } 110 | 111 | pub fn put_inbound(&mut self, from: SocketAddr, data: Vec) -> bool { 112 | if !self.nat { 113 | self.inbound.push_back((from, data)); 114 | return true; 115 | } 116 | if let Some(timeout) = self.nat_peers.get(&from) { 117 | if *timeout >= MockTimeSource::now() { 118 | self.inbound.push_back((from, data)); 119 | return true; 120 | } 121 | } 122 | warn!("Sender {:?} is filtered out by NAT", from); 123 | false 124 | } 125 | 126 | pub fn pop_outbound(&mut self) -> Option<(SocketAddr, Vec)> { 127 | self.outbound.pop_front() 128 | } 129 | } 130 | 131 | impl AsRawFd for MockSocket { 132 | fn as_raw_fd(&self) -> RawFd { 133 | unimplemented!() 134 | } 135 | } 136 | 137 | impl Socket for MockSocket { 138 | fn listen(addr: &str) -> Result { 139 | Ok(Self::new(mapped_addr(parse_listen(addr, DEFAULT_PORT)))) 140 | } 141 | 142 | fn receive(&mut self, buffer: &mut MsgBuffer) -> Result { 143 | if let Some((addr, data)) = self.inbound.pop_front() { 144 | buffer.clear(); 145 | buffer.set_length(data.len()); 146 | buffer.message_mut().copy_from_slice(&data); 147 | Ok(addr) 148 | } else { 149 | Err(io::Error::new(ErrorKind::Other, "nothing in queue")) 150 | } 151 | } 152 | 153 | fn send(&mut self, data: &[u8], addr: SocketAddr) -> Result { 154 | self.outbound.push_back((addr, data.into())); 155 | if self.nat { 156 | self.nat_peers.insert(addr, MockTimeSource::now() + 300); 157 | } 158 | Ok(data.len()) 159 | } 160 | 161 | fn address(&self) -> Result { 162 | Ok(self.address) 163 | } 164 | 165 | fn create_port_forwarding(&self) -> Option { 166 | None 167 | } 168 | } 169 | 170 | #[cfg(feature = "bench")] 171 | mod bench { 172 | use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket}; 173 | use test::Bencher; 174 | 175 | #[bench] 176 | fn udp_send(b: &mut Bencher) { 177 | let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); 178 | let data = [0; 1400]; 179 | let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1); 180 | b.iter(|| sock.send_to(&data, &addr).unwrap()); 181 | b.bytes = 1400; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/oldconfig.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | use super::{device::Type, types::Mode, util::Duration}; 6 | use crate::config::{ConfigFile, ConfigFileBeacon, ConfigFileDevice, ConfigFileStatsd, CryptoConfig}; 7 | use std::collections::HashMap; 8 | 9 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] 10 | pub enum OldCryptoMethod { 11 | #[serde(rename = "chacha20")] 12 | ChaCha20, 13 | #[serde(rename = "aes256")] 14 | AES256, 15 | #[serde(rename = "aes128")] 16 | AES128, 17 | } 18 | 19 | #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] 20 | pub struct OldConfigFile { 21 | #[serde(alias = "device-type")] 22 | pub device_type: Option, 23 | #[serde(alias = "device-name")] 24 | pub device_name: Option, 25 | #[serde(alias = "device-path")] 26 | pub device_path: Option, 27 | pub ifup: Option, 28 | pub ifdown: Option, 29 | pub crypto: Option, 30 | #[serde(alias = "shared-key")] 31 | pub shared_key: Option, 32 | pub magic: Option, 33 | pub port: Option, 34 | pub listen: Option, 35 | pub peers: Option>, 36 | #[serde(alias = "peer-timeout")] 37 | pub peer_timeout: Option, 38 | pub keepalive: Option, 39 | #[serde(alias = "beacon-store")] 40 | pub beacon_store: Option, 41 | #[serde(alias = "beacon-load")] 42 | pub beacon_load: Option, 43 | #[serde(alias = "beacon-interval")] 44 | pub beacon_interval: Option, 45 | pub mode: Option, 46 | #[serde(alias = "dst-timeout")] 47 | pub dst_timeout: Option, 48 | pub subnets: Option>, 49 | #[serde(alias = "port-forwarding")] 50 | pub port_forwarding: Option, 51 | #[serde(alias = "pid-file")] 52 | pub pid_file: Option, 53 | #[serde(alias = "stats-file")] 54 | pub stats_file: Option, 55 | #[serde(alias = "statsd-server")] 56 | pub statsd_server: Option, 57 | #[serde(alias = "statsd-prefix")] 58 | pub statsd_prefix: Option, 59 | pub user: Option, 60 | pub group: Option, 61 | } 62 | 63 | impl OldConfigFile { 64 | #[allow(clippy::or_fun_call)] 65 | pub fn convert(self) -> ConfigFile { 66 | if self.device_type.is_none() { 67 | warn!("The default device type changed from TAP to TUN") 68 | } 69 | if self.ifup.is_some() { 70 | info!("There is a new option --ip that can handle most use cases of --ifup") 71 | } 72 | info!("The converted config enables all available encryption algorithms"); 73 | if self.shared_key.is_none() { 74 | warn!("Operation without a password is no longer supported, password set to 'none'"); 75 | } 76 | if self.magic.is_some() { 77 | warn!("The magic header functionality is no longer supported") 78 | } 79 | if self.listen.is_some() && self.port.is_some() { 80 | warn!("The port option is no longer available, using listen instead") 81 | } 82 | if self.peer_timeout.is_none() { 83 | info!("The default peer timeout changed from 10 minutes to 5 minutes") 84 | } 85 | warn!("Even with a converted config file version 2 nodes can not communicate with version 1 nodes"); 86 | ConfigFile { 87 | auto_claim: None, 88 | beacon: Some(ConfigFileBeacon { 89 | interval: self.beacon_interval, 90 | load: self.beacon_load, 91 | store: self.beacon_store, 92 | password: self.shared_key.clone(), 93 | }), 94 | claims: self.subnets, 95 | crypto: CryptoConfig { 96 | algorithms: vec![], 97 | password: Some(self.shared_key.unwrap_or_else(|| "none".to_string())), 98 | private_key: None, 99 | public_key: None, 100 | trusted_keys: vec![], 101 | }, 102 | device: Some(ConfigFileDevice { 103 | fix_rp_filter: None, 104 | name: self.device_name, 105 | path: self.device_path, 106 | type_: self.device_type, 107 | }), 108 | group: self.group, 109 | ifdown: self.ifdown, 110 | ifup: self.ifup, 111 | ip: None, 112 | advertise_addresses: None, 113 | keepalive: self.keepalive, 114 | listen: self.listen.or(self.port.map(|p| format!("{}", p))), 115 | mode: self.mode, 116 | peer_timeout: self.peer_timeout, 117 | peers: self.peers, 118 | pid_file: self.pid_file, 119 | port_forwarding: self.port_forwarding, 120 | stats_file: self.stats_file, 121 | statsd: Some(ConfigFileStatsd { prefix: self.statsd_prefix, server: self.statsd_server }), 122 | switch_timeout: self.dst_timeout, 123 | user: self.user, 124 | hook: None, 125 | hooks: HashMap::new(), 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/payload.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | use crate::{error::Error, types::Address}; 6 | use std::io::{Cursor, Read}; 7 | 8 | pub trait Protocol: Sized { 9 | fn parse(_: &[u8]) -> Result<(Address, Address), Error>; 10 | } 11 | 12 | /// An ethernet frame dissector 13 | /// 14 | /// This dissector is able to extract the source and destination addresses of ethernet frames. 15 | /// 16 | /// If the ethernet frame contains a VLAN tag, both addresses will be prefixed with that tag, 17 | /// resulting in 8-byte addresses. Additional nested tags will be ignored. 18 | pub struct Frame; 19 | 20 | impl Protocol for Frame { 21 | /// Parses an ethernet frame and extracts the source and destination addresses 22 | /// 23 | /// # Errors 24 | /// This method will fail when the given data is not a valid ethernet frame. 25 | fn parse(data: &[u8]) -> Result<(Address, Address), Error> { 26 | // HOT PATH 27 | let mut cursor = Cursor::new(data); 28 | let mut src = [0; 16]; 29 | let mut dst = [0; 16]; 30 | let mut proto = [0; 2]; 31 | cursor 32 | .read_exact(&mut dst[..6]) 33 | .and_then(|_| cursor.read_exact(&mut src[..6])) 34 | .and_then(|_| cursor.read_exact(&mut proto)) 35 | .map_err(|_| Error::Parse("Frame is too short"))?; 36 | if proto == [0x81, 0x00] { 37 | src.copy_within(..6, 2); 38 | dst.copy_within(..6, 2); 39 | cursor.read_exact(&mut src[..2]).map_err(|_| Error::Parse("Vlan frame is too short"))?; 40 | src[0] &= 0x0f; // restrict vlan id to 12 bits 41 | dst[..2].copy_from_slice(&src[..2]); 42 | if src[0..1] == [0, 0] { 43 | // treat vlan id 0x000 as untagged 44 | src.copy_within(2..8, 0); 45 | dst.copy_within(2..8, 0); 46 | return Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 })); 47 | } 48 | Ok((Address { data: src, len: 8 }, Address { data: dst, len: 8 })) 49 | } else { 50 | Ok((Address { data: src, len: 6 }, Address { data: dst, len: 6 })) 51 | } 52 | } 53 | } 54 | 55 | #[test] 56 | fn decode_frame_without_vlan() { 57 | let data = [6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8]; 58 | let (src, dst) = Frame::parse(&data).unwrap(); 59 | assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 6 }); 60 | assert_eq!(dst, Address { data: [6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 6 }); 61 | } 62 | 63 | #[test] 64 | fn decode_frame_with_vlan() { 65 | 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]; 66 | let (src, dst) = Frame::parse(&data).unwrap(); 67 | assert_eq!(src, Address { data: [4, 210, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0], len: 8 }); 68 | assert_eq!(dst, Address { data: [4, 210, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], len: 8 }); 69 | } 70 | 71 | #[test] 72 | fn decode_invalid_frame() { 73 | 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()); 74 | // truncated frame 75 | assert!(Frame::parse(&[]).is_err()); 76 | // truncated vlan frame 77 | assert!(Frame::parse(&[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 0x81, 0x00]).is_err()); 78 | } 79 | 80 | /// An IP packet dissector 81 | /// 82 | /// This dissector is able to extract the source and destination ip addresses of ipv4 packets and 83 | /// ipv6 packets. 84 | #[allow(dead_code)] 85 | pub struct Packet; 86 | 87 | impl Protocol for Packet { 88 | /// Parses an ip packet and extracts the source and destination addresses 89 | /// 90 | /// # Errors 91 | /// This method will fail when the given data is not a valid ipv4 and ipv6 packet. 92 | fn parse(data: &[u8]) -> Result<(Address, Address), Error> { 93 | // HOT PATH 94 | if data.is_empty() { 95 | return Err(Error::Parse("Empty header")); 96 | } 97 | let version = data[0] >> 4; 98 | match version { 99 | 4 => { 100 | if data.len() < 20 { 101 | return Err(Error::Parse("Truncated IPv4 header")); 102 | } 103 | let src = Address::read_from_fixed(&data[12..], 4)?; 104 | let dst = Address::read_from_fixed(&data[16..], 4)?; 105 | Ok((src, dst)) 106 | } 107 | 6 => { 108 | if data.len() < 40 { 109 | return Err(Error::Parse("Truncated IPv6 header")); 110 | } 111 | let src = Address::read_from_fixed(&data[8..], 16)?; 112 | let dst = Address::read_from_fixed(&data[24..], 16)?; 113 | Ok((src, dst)) 114 | } 115 | _ => Err(Error::Parse("Invalid IP protocol version")), 116 | } 117 | } 118 | } 119 | 120 | #[test] 121 | fn decode_ipv4_packet() { 122 | let data = [0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1, 2]; 123 | let (src, dst) = Packet::parse(&data).unwrap(); 124 | assert_eq!(src, Address { data: [192, 168, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 4 }); 125 | assert_eq!(dst, Address { data: [192, 168, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 4 }); 126 | } 127 | 128 | #[test] 129 | fn decode_ipv6_packet() { 130 | let data = [ 131 | 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, 132 | 4, 3, 2, 1, 133 | ]; 134 | let (src, dst) = Packet::parse(&data).unwrap(); 135 | assert_eq!(src, Address { data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6], len: 16 }); 136 | assert_eq!(dst, Address { data: [0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1], len: 16 }); 137 | } 138 | 139 | #[test] 140 | fn decode_invalid_packet() { 141 | 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()); 142 | assert!(Packet::parse(&[ 143 | 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, 144 | 4, 3, 2, 1 145 | ]) 146 | .is_ok()); 147 | // no data 148 | assert!(Packet::parse(&[]).is_err()); 149 | // wrong version 150 | assert!(Packet::parse(&[0x20]).is_err()); 151 | // truncated ipv4 152 | assert!(Packet::parse(&[0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1, 1, 192, 168, 1]).is_err()); 153 | // truncated ipv6 154 | assert!(Packet::parse(&[ 155 | 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, 156 | 4, 3, 2 157 | ]) 158 | .is_err()); 159 | } 160 | -------------------------------------------------------------------------------- /src/poll/epoll.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | use std::{io, os::unix::io::RawFd}; 6 | 7 | use super::WaitResult; 8 | 9 | pub struct EpollWait { 10 | poll_fd: RawFd, 11 | event: libc::epoll_event, 12 | socket: RawFd, 13 | device: RawFd, 14 | timeout: u32, 15 | } 16 | 17 | impl EpollWait { 18 | pub fn new(socket: RawFd, device: RawFd, timeout: u32) -> io::Result { 19 | Self::create(socket, device, timeout, libc::EPOLLIN as u32) 20 | } 21 | 22 | pub fn testing(socket: RawFd, device: RawFd, timeout: u32) -> io::Result { 23 | Self::create(socket, device, timeout, (libc::EPOLLIN | libc::EPOLLOUT) as u32) 24 | } 25 | 26 | fn create(socket: RawFd, device: RawFd, timeout: u32, flags: u32) -> io::Result { 27 | let mut event = libc::epoll_event { u64: 0, events: 0 }; 28 | let poll_fd = unsafe { libc::epoll_create(3) }; 29 | if poll_fd == -1 { 30 | return Err(io::Error::last_os_error()); 31 | } 32 | for fd in &[socket, device] { 33 | event.u64 = *fd as u64; 34 | event.events = flags; 35 | let res = unsafe { libc::epoll_ctl(poll_fd, libc::EPOLL_CTL_ADD, *fd, &mut event) }; 36 | if res == -1 { 37 | return Err(io::Error::last_os_error()); 38 | } 39 | } 40 | Ok(Self { poll_fd, event, socket, device, timeout }) 41 | } 42 | } 43 | 44 | impl Drop for EpollWait { 45 | fn drop(&mut self) { 46 | unsafe { libc::close(self.poll_fd) }; 47 | } 48 | } 49 | 50 | impl Iterator for EpollWait { 51 | type Item = WaitResult; 52 | 53 | fn next(&mut self) -> Option { 54 | Some(match unsafe { libc::epoll_wait(self.poll_fd, &mut self.event, 1, self.timeout as i32) } { 55 | -1 => WaitResult::Error(io::Error::last_os_error()), 56 | 0 => WaitResult::Timeout, 57 | 1 => { 58 | if self.event.u64 == self.socket as u64 { 59 | WaitResult::Socket 60 | } else if self.event.u64 == self.device as u64 { 61 | WaitResult::Device 62 | } else { 63 | unreachable!() 64 | } 65 | } 66 | _ => unreachable!(), 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/poll/mod.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | #[cfg(any(target_os = "linux", target_os = "android"))] 6 | mod epoll; 7 | 8 | #[cfg(any(target_os = "linux", target_os = "android"))] 9 | pub use self::epoll::EpollWait as WaitImpl; 10 | 11 | use std::io; 12 | 13 | pub enum WaitResult { 14 | Timeout, 15 | Socket, 16 | Device, 17 | Error(io::Error), 18 | } 19 | -------------------------------------------------------------------------------- /src/port_forwarding.rs: -------------------------------------------------------------------------------- 1 | // VpnCloud - Peer-to-Peer VPN 2 | // Copyright (C) 2015-2021 Dennis Schwerdel 3 | // This software is licensed under GPL-3 or newer (see LICENSE.md) 4 | 5 | #[cfg(feature = "nat")] 6 | mod internal { 7 | 8 | use std::{io, net::SocketAddrV4}; 9 | 10 | use igd::{search_gateway, AddAnyPortError, AddPortError, Gateway, PortMappingProtocol, SearchError}; 11 | 12 | use crate::util::{get_internal_ip, SystemTimeSource, Time, TimeSource}; 13 | 14 | const LEASE_TIME: u32 = 1800; 15 | 16 | const DESCRIPTION: &str = "VpnCloud"; 17 | 18 | pub struct PortForwarding { 19 | pub internal_addr: SocketAddrV4, 20 | pub external_addr: SocketAddrV4, 21 | gateway: Gateway, 22 | pub next_extension: Option