diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 408c0f3..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,4 +0,0 @@ -[env] -# Each interface needs 1 IP allocated to the WireGuard peer IP. -# "8" = 7 tunnels per protocol. -SMOLTCP_IFACE_MAX_ADDR_COUNT = "8" diff --git a/.github/ci/macos-install-packages b/.github/ci/macos-install-packages index a4ece81..afb1102 100755 --- a/.github/ci/macos-install-packages +++ b/.github/ci/macos-install-packages @@ -1,6 +1,6 @@ #!/bin/sh -# brew install asciidoctor +brew install asciidoctor -# brew install openssl@1.1 -# cp /usr/local/opt/openssl@1.1/lib/pkgconfig/*.pc /usr/local/lib/pkgconfig/ +brew install openssl@1.1 +cp /usr/local/opt/openssl@1.1/lib/pkgconfig/*.pc /usr/local/lib/pkgconfig/ diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 382be60..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - rebase-strategy: "disabled" diff --git a/.github/onetun.png b/.github/onetun.png deleted file mode 100644 index 24bfcc6..0000000 Binary files a/.github/onetun.png and /dev/null differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e216219..fb404a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: matrix: rust: - stable - - 1.80.0 + - 1.55.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -26,12 +26,6 @@ jobs: with: command: check - - name: Run cargo check without default features - uses: actions-rs/cargo@v1 - with: - command: check - args: --no-default-features - test: name: Test Suite runs-on: ubuntu-latest @@ -39,7 +33,7 @@ jobs: matrix: rust: - stable - - 1.80.0 + - 1.55.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39b9be6..31848d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,35 +4,6 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' jobs: - docker-push: - name: docker-push - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: 1 - - - name: Log in to Docker Hub - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: aramperes/onetun - - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - create-release: name: create-release runs-on: ubuntu-latest @@ -61,7 +32,7 @@ jobs: run: echo "${{ env.VERSION }}" > artifacts/release-version - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v1 with: name: artifacts path: artifacts @@ -75,28 +46,20 @@ jobs: RUST_BACKTRACE: 1 strategy: matrix: - build: [ linux-amd64, linux-aarch64, macos-aarch64, windows ] + build: [ linux-amd64, macos-intel, windows ] include: - build: linux-amd64 - os: ubuntu-latest + os: ubuntu-18.04 rust: stable target: x86_64-unknown-linux-musl - cross: true - - build: linux-aarch64 - os: ubuntu-latest - rust: stable - target: aarch64-unknown-linux-musl - cross: true - - build: macos-aarch64 + - build: macos-intel os: macos-latest rust: stable - target: aarch64-apple-darwin - cross: false + target: x86_64-apple-darwin - build: windows os: windows-2019 rust: stable target: x86_64-pc-windows-msvc - cross: false steps: - name: Checkout repository @@ -105,7 +68,7 @@ jobs: fetch-depth: 1 - name: Install packages (Ubuntu) - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-18.04' run: | .github/ci/ubuntu-install-packages - name: Install packages (macOS) @@ -121,7 +84,7 @@ jobs: target: ${{ matrix.target }} - name: Get release download URL - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v1 with: name: artifacts path: artifacts @@ -134,24 +97,17 @@ jobs: echo "release upload url: $release_upload_url" - name: Build onetun binary - shell: bash - run: | - if [ "${{ matrix.cross }}" = "true" ]; then - cargo install cross - cross build --release --target ${{ matrix.target }} - else - cargo build --release --target ${{ matrix.target }} - fi + run: cargo build --release - name: Prepare onetun binary shell: bash run: | mkdir -p ci/assets if [ "${{ matrix.build }}" = "windows" ]; then - cp "target/${{ matrix.target }}/release/onetun.exe" "ci/assets/onetun.exe" + cp "target/release/onetun.exe" "ci/assets/onetun.exe" echo "ASSET=onetun.exe" >> $GITHUB_ENV else - cp "target/${{ matrix.target }}/release/onetun" "ci/assets/onetun-${{ matrix.build }}" + cp "target/release/onetun" "ci/assets/onetun-${{ matrix.build }}" echo "ASSET=onetun-${{ matrix.build }}" >> $GITHUB_ENV fi diff --git a/.gitignore b/.gitignore index 7a1777c..6adbcee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ /target /.idea .envrc -*.log -*.pcap -.DS_Store diff --git a/Cargo.lock b/Cargo.lock index f8e12f2..46d5ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,101 +1,80 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] -name = "async-recursion" -version = "1.1.1" +name = "ascii" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" [[package]] -name = "async-trait" -version = "0.1.87" +name = "atty" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "proc-macro2", - "quote", - "syn", + "hermit-abi", + "libc", + "winapi", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets", ] [[package]] name = "base64" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" @@ -103,80 +82,61 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "boringtun" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751787b019c674b9ac353f4eaa285e6711c21badb421cd8c199bf2c83b727f29" +version = "0.3.0" +source = "git+https://github.com/cloudflare/boringtun?branch=master#fbcf2689e7776a5af805c5a38feb5c8988829980" dependencies = [ - "aead", "base64", - "blake2", - "chacha20poly1305", + "clap", + "daemonize", "hex", - "hmac", "ip_network", "ip_network_table", + "jni", "libc", - "nix", "parking_lot", - "rand_core", "ring", - "tracing", - "untrusted 0.9.0", - "x25519-dalek", + "slog", + "slog-term", + "untrusted", ] +[[package]] +name = "boxfnonce" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" + [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.9.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.2.2" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" -dependencies = [ - "shlex", -] +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" @@ -185,197 +145,108 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chacha20" -version = "0.9.1" +name = "chrono" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", + "libc", + "num-integer", + "num-traits", + "time", + "winapi", ] [[package]] name = "clap" -version = "4.5.21" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" -dependencies = [ - "anstyle", - "clap_lex", + "bitflags", "strsim", - "terminal_size", + "textwrap", + "unicode-width", ] [[package]] -name = "clap_lex" -version = "0.7.3" +name = "combine" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] [[package]] -name = "cpufeatures" -version = "0.2.16" +name = "daemonize" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" dependencies = [ + "boxfnonce", "libc", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] - -[[package]] -name = "curve25519-dalek" -version = "4.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "fiat-crypto", - "platforms", - "rustc_version", - "subtle", - "zeroize", + "dirs-sys-next", ] [[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" +name = "dirs-sys-next" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libc", + "redox_users", + "winapi", ] [[package]] -name = "defmt" -version = "0.3.10" +name = "either" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "defmt-parser" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" -dependencies = [ - "thiserror", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" -version = "0.10.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" dependencies = [ + "atty", "humantime", - "is-terminal", "log", "regex", "termcolor", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "error-chain" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ - "libc", - "windows-sys 0.59.0", + "backtrace", + "version_check", ] -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "futures" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" dependencies = [ "futures-channel", "futures-core", @@ -388,9 +259,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" dependencies = [ "futures-core", "futures-sink", @@ -398,15 +269,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" dependencies = [ "futures-core", "futures-task", @@ -415,16 +286,18 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" dependencies = [ + "autocfg", + "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -432,22 +305,23 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" dependencies = [ + "autocfg", "futures-channel", "futures-core", "futures-io", @@ -457,24 +331,16 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", + "proc-macro-hack", + "proc-macro-nested", "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", @@ -483,40 +349,18 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] [[package]] name = "hex" @@ -524,51 +368,35 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "humantime" -version = "2.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "indexmap" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ - "equivalent", - "hashbrown", + "quick-error", ] [[package]] -name = "inout" -version = "0.1.3" +name = "instant" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" dependencies = [ - "generic-array", + "cfg-if", ] [[package]] name = "ip_network" -version = "0.4.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" +checksum = "2ee15951c035f79eddbef745611ec962f63f4558f1dadf98ab723cc603487c6f" [[package]] name = "ip_network_table" -version = "0.2.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" +checksum = "fa59c26e61245c366c7d615b56524cdc7c9104ba15c0ebfc0f09ddb8cdf728be" dependencies = [ "ip_network", "ip_network_table-deps-treebitmap", @@ -581,53 +409,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" [[package]] -name = "is-terminal" -version = "0.4.13" +name = "jni" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "1ecfa3b81afc64d9a6539c4eece96ac9a93c551c713a313800dade8e33d7b5c1" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", + "cesu8", + "combine", + "error-chain", + "jni-sys", + "log", + "walkdir", ] [[package]] -name = "js-sys" -version = "0.3.74" +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ - "once_cell", "wasm-bindgen", ] [[package]] -name = "libc" -version = "0.2.170" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "linux-raw-sys" -version = "0.4.14" +name = "libc" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ - "autocfg", "scopeguard", ] [[package]] -name = "log" -version = "0.4.22" +name = "lockfree" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "74ee94b5ad113c7cb98c5a040f783d0952ee4fe100993881d1673c2cb002dd23" +dependencies = [ + "owned-alloc", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] [[package]] name = "managed" @@ -637,129 +484,146 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.4" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ - "adler2", + "adler", + "autocfg", ] [[package]] name = "mio" -version = "1.0.3" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", - "wasi", - "windows-sys 0.52.0", + "log", + "miow", + "ntapi", + "winapi", ] [[package]] -name = "nix" -version = "0.25.1" +name = "miow" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", + "num-traits", ] [[package]] -name = "nom" -version = "7.1.3" +name = "num-traits" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "memchr", - "minimal-lexical", + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] name = "object" -version = "0.36.5" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onetun" -version = "0.3.10" +version = "0.1.0" dependencies = [ "anyhow", - "async-recursion", - "async-trait", - "base64", "boringtun", - "bytes", "clap", "futures", + "lockfree", "log", - "nom", "pretty_env_logger", - "priority-queue", - "rand", "smoltcp", "tokio", - "tracing", ] [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "owned-alloc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "30fceb411f9a12ff9222c5f824026be368ff15dc2f13468d850c7d3f502205d6" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ + "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if", + "instant", "libc", "redox_syscall", "smallvec", - "windows-targets", + "winapi", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" [[package]] name = "pin-utils" @@ -767,149 +631,77 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "platforms" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43467300237085a4f9e864b937cf0bc012cef7740be12be1a48b10d2c8a3701" - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - [[package]] name = "pretty_env_logger" -version = "0.5.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" dependencies = [ + "chrono", "env_logger", "log", ] [[package]] -name = "priority-queue" -version = "2.1.1" +name = "proc-macro-hack" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" -dependencies = [ - "autocfg", - "equivalent", - "indexmap", -] +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "proc-macro-nested" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ - "unicode-ident", + "unicode-xid", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" -version = "1.0.37" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ - "bitflags 2.6.0", + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -918,9 +710,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "ring" @@ -932,270 +724,212 @@ dependencies = [ "libc", "once_cell", "spin", - "untrusted 0.7.1", + "untrusted", "web-sys", "winapi", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] -name = "rustc_version" -version = "0.4.1" +name = "rustversion" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" [[package]] -name = "rustix" -version = "0.38.41" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "winapi-util", ] [[package]] name = "scopeguard" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "semver" -version = "1.0.23" +name = "signal-hook-registry" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ - "serde_derive", + "libc", ] -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "slab" -version = "0.4.9" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-term" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c1e7e5aab61ced6006149ea772770b84a0d16ce0f7885def313e4829946d76" dependencies = [ - "autocfg", + "atty", + "chrono", + "slog", + "term", + "thread_local", ] [[package]] name = "smallvec" -version = "1.13.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smoltcp" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" +version = "0.8.0" +source = "git+https://github.com/smoltcp-rs/smoltcp?branch=master#35e833e33dfd3e4efc3eb7d5de06bec17c54b011" dependencies = [ - "bitflags 1.3.2", + "bitflags", "byteorder", - "cfg-if", - "defmt", - "heapless", + "libc", "log", "managed", ] -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strsim" -version = "0.11.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "2.0.90" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "unicode-xid", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", ] [[package]] name = "termcolor" -version = "1.4.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] [[package]] -name = "terminal_size" -version = "0.4.1" +name = "textwrap" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "rustix", - "windows-sys 0.59.0", + "unicode-width", ] [[package]] -name = "thiserror" -version = "2.0.3" +name = "thread_local" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio" -version = "1.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" dependencies = [ "once_cell", ] [[package]] -name = "typenum" -version = "1.17.0" +name = "time" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ - "crypto-common", - "subtle", + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "tokio" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", ] [[package]] @@ -1205,43 +939,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "untrusted" -version = "0.9.0" +name = "version_check" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] -name = "version_check" -version = "0.9.5" +name = "void" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if", - "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", + "lazy_static", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1250,9 +994,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1260,9 +1004,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", @@ -1273,15 +1017,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", @@ -1305,11 +1049,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "windows-sys 0.59.0", + "winapi", ] [[package]] @@ -1317,138 +1061,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "x25519-dalek" -version = "2.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" -dependencies = [ - "curve25519-dalek", - "rand_core", - "serde", - "zeroize", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index ffb8a64..9f288a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,48 +1,17 @@ [package] name = "onetun" -version = "0.3.10" -edition = "2021" -license = "MIT" -description = "A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations." -authors = ["Aram Peres "] -repository = "https://github.com/aramperes/onetun" +version = "0.1.0" +edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# Required dependencies (bin and lib) -boringtun = { version = "0.6.0", default-features = false } +boringtun = { git = "https://github.com/cloudflare/boringtun", branch = "master" } +clap = { version = "2.33", default-features = false, features = ["suggestions"] } log = "0.4" +pretty_env_logger = "0.3" anyhow = "1" -tokio = { version = "1", features = [ "rt", "sync", "io-util", "net", "time", "fs", "macros" ] } -futures = "0.3" -rand = "0.8" -nom = "7" -async-trait = "0.1" -priority-queue = "2.1" -smoltcp = { version = "0.12", default-features = false, features = [ - "std", - "log", - "medium-ip", - "proto-ipv4", - "proto-ipv6", - "socket-udp", - "socket-tcp", -] } -bytes = "1" -base64 = "0.13" - -# forward boringtuns tracing events to log -tracing = { version = "0.1", default-features = false, features = ["log"] } - -# bin-only dependencies -clap = { version = "4.4.11", default-features = false, features = ["suggestions", "std", "env", "help", "wrap_help"], optional = true } -pretty_env_logger = { version = "0.5", optional = true } -async-recursion = "1.0" - -[features] -pcap = [] -default = [ "bin" ] -bin = [ "clap", "pretty_env_logger", "pcap", "tokio/rt-multi-thread" ] - -[lib] +smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", branch = "master" } +tokio = { version = "1", features = ["full"] } +lockfree = "0.5.1" +futures = "0.3.17" diff --git a/Dockerfile b/Dockerfile index 90f851d..bd01361 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM rust:1.82.0 as cargo-build +FROM rust:1.55 as cargo-build +RUN apt-get update WORKDIR /usr/src/onetun COPY Cargo.toml Cargo.toml # Placeholder to download dependencies and cache them using layering RUN mkdir src/ -RUN touch src/lib.rs RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs RUN cargo build --release RUN rm -f target/x86_64-unknown-linux-musl/release/deps/myapp* @@ -15,13 +15,11 @@ COPY . . RUN cargo build --release FROM debian:11-slim -RUN apt-get update \ - && apt-get install dumb-init -y \ - && rm -rf /var/lib/apt/lists/* COPY --from=cargo-build /usr/src/onetun/target/release/onetun /usr/local/bin/onetun # Run as non-root +RUN chown 1000 /usr/local/bin/onetun USER 1000 -ENTRYPOINT ["dumb-init", "/usr/local/bin/onetun"] +ENTRYPOINT ["/usr/local/bin/onetun"] diff --git a/LICENSE b/LICENSE index d57c948..7479b85 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Aram Peres +Copyright (c) 2021 Aram Peres Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 58204f6..129ffea 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,23 @@ -onetun - # onetun -A cross-platform, user-space WireGuard port-forwarder that requires **no root-access or system network configurations**. +A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations. -[![crates.io](https://img.shields.io/crates/v/onetun.svg)](https://crates.io/crates/onetun) -[![MIT licensed](https://img.shields.io/crates/l/onetun.svg)](./LICENSE) -[![Build status](https://github.com/aramperes/onetun/actions/workflows/build.yml/badge.svg)](https://github.com/aramperes/onetun/actions) -[![Latest Release](https://img.shields.io/github/v/tag/aramperes/onetun?label=release)](https://github.com/aramperes/onetun/releases/latest) +## How it works -## Use-case +**onetun** opens a TCP port on your local system, from which traffic is forwarded to a TCP port on a peer in your +WireGuard network. It requires no changes to your operating system's network interfaces. -Access TCP or UDP services running on your WireGuard network, from devices that don't have WireGuard installed. +The only prerequisite is to register a peer IP and public key on your WireGuard endpoint; those are necessary for the +WireGuard endpoint to trust the onetun peer and for packets to be routed. -For example, - -- Personal or shared computers where you can't install WireGuard (root) -- IoT and mobile devices -- Root-less containers - -## Download - -onetun is available to install from [crates.io](https://crates.io/crates/onetun) with Rust ≥1.80.0: - -```shell -cargo install onetun ``` - -You can also download the binary for Windows, macOS (Apple Silicon), and Linux (amd64, arm64) from -the [Releases](https://github.com/aramperes/onetun/releases) page. - -You can also run onetun using [Docker](https://hub.docker.com/r/aramperes/onetun): - -```shell -docker run --rm --name onetun --user 1000 -p 8080:8080 aramperes/onetun \ - 0.0.0.0:8080:192.168.4.2:8080 [...options...] -``` - -You can also build onetun locally, using Rust ≥1.80.0: - -```shell -git clone https://github.com/aramperes/onetun && cd onetun -cargo build --release -./target/release/onetun -``` - -## Usage - -**onetun** opens a TCP or UDP port on your local system, from which traffic is forwarded to a port on a peer in your -WireGuard network. It requires no changes to your operating system's network interfaces: you don't need to have `root` -access, or install any WireGuard tool on your local system for it to work. - -The only prerequisite is to register a peer IP and public key on the remote WireGuard endpoint; those are necessary for -the WireGuard endpoint to trust the onetun peer and for packets to be routed. - -```shell -onetun [src_host:]::[:TCP,UDP,...] [...] \ - --endpoint-addr \ - --endpoint-public-key \ - --private-key \ - --source-peer-ip \ - --keep-alive \ - --log +./onetun \ + --endpoint-addr \ + --endpoint-public-key \ + --private-key \ + --source-peer-ip \ + --keep-alive \ + --log Note: you can use environment variables for all of these flags. Use `onetun --help` for details. @@ -70,7 +26,7 @@ onetun [src_host:]::[:TCP,UDP,...] [...] \ Suppose your WireGuard endpoint has the following configuration, and is accessible from `140.30.3.182:51820`: -```shell +``` # /etc/wireguard/wg0.conf [Interface] @@ -93,7 +49,7 @@ We want to access a web server on the friendly peer (`192.168.4.2`) on port `808 local port, say `127.0.0.1:8080`, that will tunnel through WireGuard to reach the peer web server: ```shell -onetun 127.0.0.1:8080:192.168.4.2:8080 \ +./onetun 127.0.0.1:8080 192.168.4.2:8080 \ --endpoint-addr 140.30.3.182:51820 \ --endpoint-public-key 'PUB_****************************************' \ --private-key 'PRIV_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' \ @@ -103,220 +59,17 @@ onetun 127.0.0.1:8080:192.168.4.2:8080 \ You'll then see this log: -```shell -INFO onetun > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) +``` +INFO onetun > Tunnelling [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) ``` Which means you can now access the port locally! -```shell -curl 127.0.0.1:8080 +``` +$ curl 127.0.0.1:8080 Hello world! ``` -### Multiple tunnels in parallel - -**onetun** supports running multiple tunnels in parallel. For example: - -```shell -onetun 127.0.0.1:8080:192.168.4.2:8080 127.0.0.1:8081:192.168.4.4:8081 -INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8081]->[192.168.4.4:8081] (via [140.30.3.182:51820] as peer 192.168.4.3) -``` - -... would open TCP ports 8080 and 8081 locally, which forward to their respective ports on the different peers. - -#### Maximum number of tunnels - -`smoltcp` imposes a compile-time limit on the number of IP addresses assigned to an interface. **onetun** increases -the default value to support most use-cases. In effect, the default limit on the number of **onetun** peers -is **7 per protocol** (TCP and UDP). - -Should you need more unique IP addresses to forward ports to, you can increase the limit in `.cargo/config.toml` and recompile **onetun**. - -### UDP Support - -**onetun** supports UDP forwarding. You can add `:UDP` at the end of the port-forward configuration, or `UDP,TCP` to support -both protocols on the same port (note that this opens 2 separate tunnels, just on the same port) - -```shell -onetun 127.0.0.1:8080:192.168.4.2:8080:UDP -INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) - -onetun 127.0.0.1:8080:192.168.4.2:8080:UDP,TCP -INFO onetun::tunnel > Tunneling UDP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -``` - -Note: UDP support is totally experimental. You should read the UDP portion of the **Architecture** section before using -it in any production capacity. - -### IPv6 Support - -**onetun** supports both IPv4 and IPv6. In fact, you can use onetun to forward some IP version to another, e.g. 6-to-4: - -```shell -onetun [::1]:8080:192.168.4.2:8080 -INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -``` - -Note that each tunnel can only support one "source" IP version and one "destination" IP version. If you want to support -both IPv4 and IPv6 on the same port, you should create a second port-forward: - -```shell -onetun [::1]:8080:192.168.4.2:8080 127.0.0.1:8080:192.168.4.2:8080 -INFO onetun::tunnel > Tunneling TCP [[::1]:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -``` - -### Packet Capture - -For debugging purposes, you can enable the capture of IP packets sent between onetun and the WireGuard peer. -The output is a libpcap capture file that can be viewed with Wireshark. - -```shell -onetun --pcap wg.pcap 127.0.0.1:8080:192.168.4.2:8080 -INFO onetun::pcap > Capturing WireGuard IP packets to wg.pcap -INFO onetun::tunnel > Tunneling TCP [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3) -``` - -To capture packets sent to and from the onetun local port, you must use an external tool like `tcpdump` with root access: - -```shell -sudo tcpdump -i lo -w local.pcap 'dst 127.0.0.1 && port 8080' -``` - -### WireGuard Options - -By default, onetun will create the UDP socket to communicate with the WireGuard endpoint on all interfaces and on a dynamic port, -i.e. `0.0.0.0:0` for IPv4 endpoints, or `[::]:0` for IPv6. -You can bind to a static address instead using `--endpoint-bind-addr`: - -```shell -onetun --endpoint-bind-addr 0.0.0.0:51820 --endpoint-addr 140.30.3.182:51820 [...] -``` - -The security of the WireGuard connection can be further enhanced with a **pre-shared key** (PSK). You can generate such a key with the `wg genpsk` command, and provide it using `--preshared-key`. -The peer must also have this key configured using the `PresharedKey` option. - -```shell -onetun --preshared-key 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' [...] -``` - -## Architecture - -**In short:** onetun uses [smoltcp's](https://github.com/smoltcp-rs/smoltcp) TCP/IP and UDP stack to generate IP packets -using its state machine ("virtual interface"). The generated IP packets are -encrypted by [boringtun](https://github.com/cloudflare/boringtun) and sent to the WireGuard endpoint. Encrypted IP packets received -from the WireGuard endpoint are decrypted using boringtun and sent through the smoltcp virtual interface state machine. -onetun creates "virtual sockets" in the virtual interface to forward data sent from inbound connections, -as well as to receive data from the virtual interface to forward back to the local client. - ---- - -onetun uses [tokio](https://github.com/tokio-rs/tokio), the async runtime, to listen for new TCP connections on the -given port. - -When a client connects to the onetun's TCP port, a "virtual client" is -created in a [smoltcp](https://github.com/smoltcp-rs/smoltcp) "virtual" TCP/IP interface, which runs fully inside the onetun -process. An ephemeral "virtual port" is assigned to the "virtual client", which maps back to the local client. - -When the real client opens the connection, the virtual client socket opens a TCP connection to the virtual server -(a dummy socket bound to the remote host/port). The virtual interface in turn crafts the `SYN` segment and wraps it in an IP packet. -Because of how the virtual client and server are configured, the IP packet is crafted with a source address -being the configured `source-peer-ip` (`192.168.4.3` in the example above), -and the destination address matches the port-forward's configured destination (`192.168.4.2`). - -By doing this, we let smoltcp handle the crafting of the IP packets, and the handling of the client's TCP states. -Instead of actually sending those packets to the virtual server, -we can intercept them in the virtual interface and encrypt the packets using [boringtun](https://github.com/cloudflare/boringtun), -and send them to the WireGuard endpoint's UDP port. - -Once the WireGuard endpoint receives an encrypted IP packet, it decrypts it using its private key and reads the IP packet. -It reads the destination address, re-encrypts the IP packet using the matching peer's public key, and sends it off to -the peer's UDP endpoint. - -The peer receives the encrypted IP and decrypts it. It can then read the inner payload (the TCP segment), -forward it to the server's port, which handles the TCP segment. The TCP server responds with `SYN-ACK`, which goes back through -the peer's local WireGuard interface, gets encrypted, forwarded to the WireGuard endpoint, and then finally back to onetun's UDP port. - -When onetun receives an encrypted packet from the WireGuard endpoint, it decrypts it using boringtun. -The resulting IP packet is dispatched to the corresponding virtual interface running inside onetun; -the IP packet is then read and processed by the virtual interface, and the virtual client's TCP state is updated. - -Whenever data is sent by the real client, it is simply "sent" by the virtual client, which kicks off the whole IP encapsulation -and WireGuard encryption again. When data is sent by the real server, it ends up routed in the virtual interface, which allows -the virtual client to read it. When the virtual client reads data, it simply pushes the data back to the real client. - -This work is all made possible by [smoltcp](https://github.com/smoltcp-rs/smoltcp) and [boringtun](https://github.com/cloudflare/boringtun), -so special thanks to the developers of those libraries. - -### UDP - -UDP support is experimental. Since UDP messages are stateless, there is no perfect way for onetun to know when to release the -assigned virtual port back to the pool for a new peer to use. This would cause issues over time as running out of virtual ports -would mean new datagrams get dropped. To alleviate this, onetun will cap the amount of ports used by one peer IP address; -if another datagram comes in from a different port but with the same IP, the least recently used virtual port will be freed and assigned -to the new peer port. At that point, any datagram packets destined for the reused virtual port will be routed to the new peer, -and any datagrams received by the old peer will be dropped. - -In addition, in cases where many IPs are exhausting the UDP virtual port pool in tandem, and a totally new peer IP sends data, -onetun will have to pick the least recently used virtual port from _any_ peer IP and reuse it. However, this is only allowed -if the least recently used port hasn't been used for a certain amount of time. If all virtual ports are truly "active" -(with at least one transmission within that time limit), the new datagram gets dropped due to exhaustion. - -All in all, I would not recommend using UDP forwarding for public services, since it's most likely prone to simple DoS or DDoS. - -## HTTP/SOCKS Proxy - -**onetun** is a Transport-layer proxy (also known as port forwarding); it is not in scope to provide -a HTTP/SOCKS proxy server. However, you can easily chain **onetun** with a proxy server on a remote -that is locked down to your WireGuard network. - -For example, you could run [dante-server](https://www.inet.no/dante/) on a peer (ex. `192.168.4.2`) with the following configuration: - -``` -# /etc/danted.conf - -logoutput: syslog -user.privileged: root -user.unprivileged: nobody - -internal: 192.168.4.2 port=1080 -external: eth0 - -socksmethod: none -clientmethod: none - -# Locks down proxy use to WireGuard peers (192.168.4.x) -client pass { - from: 192.168.4.0/24 to: 0.0.0.0/0 -} -socks pass { - from: 192.168.4.0/24 to: 0.0.0.0/0 -} -``` - -Then use **onetun** to expose the SOCKS5 proxy locally: - -```shell -onetun 127.0.0.1:1080:192.168.4.2:1080 -INFO onetun::tunnel > Tunneling TCP [127.0.0.1:1080]->[192.168.4.2:1080] (via [140.30.3.182:51820] as peer 192.168.4.3) -``` - -Test with `curl` (or configure your browser): - -```shell -curl -x socks5://127.0.0.1:1080 https://ifconfig.me -``` - -## Contributing and Maintenance - -I will gladly accept contributions to onetun, and set aside time to review all pull-requests. -Please consider opening a GitHub issue if you are unsure if your contribution is within the scope of the project. - -**Disclaimer**: I do not have enough personal time to actively maintain onetun besides open-source contributions. - ## License -MIT License. See `LICENSE` for details. Copyright © 2025 Aram Peres. +MIT. See `LICENSE` for details. diff --git a/src/config.rs b/src/config.rs index 411efa3..e8b71a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,339 +1,128 @@ -use std::collections::HashSet; -use std::convert::TryFrom; -use std::fmt::{Display, Formatter}; -use std::fs::read_to_string; -use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; -use anyhow::{bail, Context}; -pub use boringtun::x25519::{PublicKey, StaticSecret}; +use anyhow::Context; +use boringtun::crypto::{X25519PublicKey, X25519SecretKey}; +use clap::{App, Arg}; -const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1"; - -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Config { - pub port_forwards: Vec, - #[allow(dead_code)] - pub remote_port_forwards: Vec, - pub private_key: Arc, - pub endpoint_public_key: Arc, - pub preshared_key: Option<[u8; 32]>, - pub endpoint_addr: SocketAddr, - pub endpoint_bind_addr: SocketAddr, - pub source_peer_ip: IpAddr, - pub keepalive_seconds: Option, - pub max_transmission_unit: usize, - pub log: String, - pub warnings: Vec, - pub pcap_file: Option, + pub(crate) source_addr: SocketAddr, + pub(crate) dest_addr: SocketAddr, + pub(crate) private_key: Arc, + pub(crate) endpoint_public_key: Arc, + pub(crate) endpoint_addr: SocketAddr, + pub(crate) source_peer_ip: IpAddr, + pub(crate) keepalive_seconds: Option, + pub(crate) log: String, } impl Config { - #[cfg(feature = "bin")] pub fn from_args() -> anyhow::Result { - use clap::{Arg, Command}; - - let mut warnings = vec![]; - - let matches = Command::new("onetun") + let matches = App::new("onetun") .author("Aram Peres ") .version(env!("CARGO_PKG_VERSION")) .args(&[ - Arg::new("PORT_FORWARD") - .required(false) - .num_args(1..) - .help("Port forward configurations. The format of each argument is [src_host:]::[:TCP,UDP,...], \ - where [src_host] is the local IP to listen on, is the local port to listen on, is the remote peer IP to forward to, and is the remote port to forward to. \ - Environment variables of the form 'ONETUN_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\ - Examples:\n\ - \t127.0.0.1:8080:192.168.4.1:8081:TCP,UDP\n\ - \t127.0.0.1:8080:192.168.4.1:8081:TCP\n\ - \t0.0.0.0:8080:192.168.4.1:8081\n\ - \t[::1]:8080:192.168.4.1:8081\n\ - \t8080:192.168.4.1:8081\n\ - \t8080:192.168.4.1:8081:TCP\n\ - \tlocalhost:8080:192.168.4.1:8081:TCP\n\ - \tlocalhost:8080:peer.intranet:8081:TCP\ - "), - Arg::new("private-key") - .conflicts_with("private-key-file") - .num_args(1) + Arg::with_name("SOURCE_ADDR") + .required(true) + .takes_value(true) + .env("ONETUN_SOURCE_ADDR") + .help("The source address (IP + port) to forward from. Example: 127.0.0.1:2115"), + Arg::with_name("DESTINATION_ADDR") + .required(true) + .takes_value(true) + .env("ONETUN_DESTINATION_ADDR") + .help("The destination address (IP + port) to forward to. The IP should be a peer registered in the Wireguard endpoint. Example: 192.168.4.2:2116"), + Arg::with_name("private-key") + .required(true) + .takes_value(true) .long("private-key") .env("ONETUN_PRIVATE_KEY") - .help("The private key of this peer. The corresponding public key should be registered in the WireGuard endpoint. \ - You can also use '--private-key-file' to specify a file containing the key instead."), - Arg::new("private-key-file") - .num_args(1) - .long("private-key-file") - .env("ONETUN_PRIVATE_KEY_FILE") - .help("The path to a file containing the private key of this peer. The corresponding public key should be registered in the WireGuard endpoint."), - Arg::new("endpoint-public-key") + .help("The private key of this peer. The corresponding public key should be registered in the Wireguard endpoint."), + Arg::with_name("endpoint-public-key") .required(true) - .num_args(1) + .takes_value(true) .long("endpoint-public-key") .env("ONETUN_ENDPOINT_PUBLIC_KEY") - .help("The public key of the WireGuard endpoint (remote)."), - Arg::new("preshared-key") - .required(false) - .num_args(1) - .long("preshared-key") - .env("ONETUN_PRESHARED_KEY") - .help("The pre-shared key (PSK) as configured with the peer."), - Arg::new("endpoint-addr") + .help("The public key of the Wireguard endpoint (remote)."), + Arg::with_name("endpoint-addr") .required(true) - .num_args(1) + .takes_value(true) .long("endpoint-addr") .env("ONETUN_ENDPOINT_ADDR") - .help("The address (IP + port) of the WireGuard endpoint (remote). Example: 1.2.3.4:51820"), - Arg::new("endpoint-bind-addr") - .required(false) - .num_args(1) - .long("endpoint-bind-addr") - .env("ONETUN_ENDPOINT_BIND_ADDR") - .help("The address (IP + port) used to bind the local UDP socket for the WireGuard tunnel. Example: 1.2.3.4:30000. Defaults to 0.0.0.0:0 for IPv4 endpoints, or [::]:0 for IPv6 endpoints."), - Arg::new("source-peer-ip") + .help("The address (IP + port) of the Wireguard endpoint (remote). Example: 1.2.3.4:51820"), + Arg::with_name("source-peer-ip") .required(true) - .num_args(1) + .takes_value(true) .long("source-peer-ip") .env("ONETUN_SOURCE_PEER_IP") .help("The source IP to identify this peer as (local). Example: 192.168.4.3"), - Arg::new("keep-alive") + Arg::with_name("keep-alive") .required(false) - .num_args(1) + .takes_value(true) .long("keep-alive") .env("ONETUN_KEEP_ALIVE") .help("Configures a persistent keep-alive for the WireGuard tunnel, in seconds."), - Arg::new("max-transmission-unit") + Arg::with_name("log") .required(false) - .num_args(1) - .long("max-transmission-unit") - .env("ONETUN_MTU") - .default_value("1420") - .help("Configures the max-transmission-unit (MTU) of the WireGuard tunnel."), - Arg::new("log") - .required(false) - .num_args(1) + .takes_value(true) .long("log") .env("ONETUN_LOG") .default_value("info") - .help("Configures the log level and format."), - Arg::new("pcap") - .required(false) - .num_args(1) - .long("pcap") - .env("ONETUN_PCAP") - .help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file."), - Arg::new("remote") - .required(false) - .num_args(1..) - .long("remote") - .short('r') - .help("Remote port forward configurations. The format of each argument is ::[:TCP,UDP,...], \ - where is the port the other peers will reach the server with, is the IP to forward to, and is the port to forward to. \ - The will be bound on onetun's peer IP, as specified by --source-peer-ip. If you pass a different value for here, it will be rejected.\n\ - Note: : must be reachable by onetun. If referring to another WireGuard peer, use --bridge instead (not supported yet).\n\ - Environment variables of the form 'ONETUN_REMOTE_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\ - Examples:\n\ - \t--remote 8080:localhost:8081:TCP,UDP\n\ - \t--remote 8080:[::1]:8081:TCP\n\ - \t--remote 8080:google.com:80\ - "), + .help("Configures the log level and format.") ]).get_matches(); - // Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs - let mut port_forward_strings = HashSet::new(); - if let Some(values) = matches.get_many::("PORT_FORWARD") { - for value in values { - port_forward_strings.insert(value.to_owned()); - } - } - for n in 1.. { - if let Ok(env) = std::env::var(format!("ONETUN_PORT_FORWARD_{}", n)) { - port_forward_strings.insert(env); - } else { - break; - } - } - - // Parse `PORT_FORWARD` strings into `PortForwardConfig` - let port_forwards: anyhow::Result>> = port_forward_strings - .into_iter() - .map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE)) - .collect(); - let port_forwards: Vec = port_forwards - .context("Failed to parse port forward config")? - .into_iter() - .flatten() - .collect(); - - // Read source-peer-ip - let source_peer_ip = parse_ip(matches.get_one::("source-peer-ip")) - .context("Invalid source peer IP")?; - - // Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs - let mut port_forward_strings = HashSet::new(); - if let Some(values) = matches.get_many::("remote") { - for value in values { - port_forward_strings.insert(value.to_owned()); - } - } - for n in 1.. { - if let Ok(env) = std::env::var(format!("ONETUN_REMOTE_PORT_FORWARD_{}", n)) { - port_forward_strings.insert(env); - } else { - break; - } - } - // Parse `PORT_FORWARD` strings into `PortForwardConfig` - let remote_port_forwards: anyhow::Result>> = - port_forward_strings - .into_iter() - .map(|s| { - PortForwardConfig::from_notation( - &s, - matches.get_one::("source-peer-ip").unwrap(), - ) - }) - .collect(); - let mut remote_port_forwards: Vec = remote_port_forwards - .context("Failed to parse remote port forward config")? - .into_iter() - .flatten() - .collect(); - for port_forward in remote_port_forwards.iter_mut() { - if port_forward.source.ip() != source_peer_ip { - bail!("Remote port forward config must match --source-peer-ip ({}), or be omitted.", source_peer_ip); - } - port_forward.source = SocketAddr::from((source_peer_ip, port_forward.source.port())); - port_forward.remote = true; - } - - if port_forwards.is_empty() && remote_port_forwards.is_empty() { - bail!("No port forward configurations given."); - } - - // Read private key from file or CLI argument - let (group_readable, world_readable) = matches - .get_one::("private-key-file") - .and_then(is_file_insecurely_readable) - .unwrap_or_default(); - if group_readable { - warnings.push("Private key file is group-readable. This is insecure.".into()); - } - if world_readable { - warnings.push("Private key file is world-readable. This is insecure.".into()); - } - - let private_key = if let Some(private_key_file) = - matches.get_one::("private-key-file") - { - read_to_string(private_key_file) - .map(|s| s.trim().to_string()) - .context("Failed to read private key file") - } else { - if std::env::var("ONETUN_PRIVATE_KEY").is_err() { - warnings.push("Private key was passed using CLI. This is insecure. \ - Use \"--private-key-file \", or the \"ONETUN_PRIVATE_KEY\" env variable instead.".into()); - } - matches - .get_one::("private-key") - .cloned() - .context("Missing private key") - }?; - - let endpoint_addr = parse_addr(matches.get_one::("endpoint-addr")) - .context("Invalid endpoint address")?; - - let endpoint_bind_addr = if let Some(addr) = matches.get_one::("endpoint-bind-addr") - { - let addr = parse_addr(Some(addr)).context("Invalid bind address")?; - // Make sure the bind address and endpoint address are the same IP version - if addr.ip().is_ipv4() != endpoint_addr.ip().is_ipv4() { - bail!("Endpoint and bind addresses must be the same IP version"); - } - addr - } else { - // Return the IP version of the endpoint address - match endpoint_addr { - SocketAddr::V4(_) => parse_addr(Some("0.0.0.0:0"))?, - SocketAddr::V6(_) => parse_addr(Some("[::]:0"))?, - } - }; - Ok(Self { - port_forwards, - remote_port_forwards, - private_key: Arc::new(parse_private_key(&private_key).context("Invalid private key")?), - endpoint_public_key: Arc::new( - parse_public_key(matches.get_one::("endpoint-public-key")) - .context("Invalid endpoint public key")?, + source_addr: parse_addr(matches.value_of("SOURCE_ADDR")) + .with_context(|| "Invalid source address")?, + dest_addr: parse_addr(matches.value_of("DESTINATION_ADDR")) + .with_context(|| "Invalid destination address")?, + private_key: Arc::new( + parse_private_key(matches.value_of("private-key")) + .with_context(|| "Invalid private key")?, ), - preshared_key: parse_preshared_key(matches.get_one::("preshared-key"))?, - endpoint_addr, - endpoint_bind_addr, - source_peer_ip, - keepalive_seconds: parse_keep_alive(matches.get_one::("keep-alive")) - .context("Invalid keep-alive value")?, - max_transmission_unit: parse_mtu(matches.get_one::("max-transmission-unit")) - .context("Invalid max-transmission-unit value")?, - log: matches - .get_one::("log") - .cloned() - .unwrap_or_default(), - pcap_file: matches.get_one::("pcap").cloned(), - warnings, + endpoint_public_key: Arc::new( + parse_public_key(matches.value_of("endpoint-public-key")) + .with_context(|| "Invalid endpoint public key")?, + ), + endpoint_addr: parse_addr(matches.value_of("endpoint-addr")) + .with_context(|| "Invalid endpoint address")?, + source_peer_ip: parse_ip(matches.value_of("source-peer-ip")) + .with_context(|| "Invalid source peer IP")?, + keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive")) + .with_context(|| "Invalid keep-alive value")?, + log: matches.value_of("log").unwrap_or_default().into(), }) } } -fn parse_addr>(s: Option) -> anyhow::Result { - s.context("Missing address")? - .as_ref() - .to_socket_addrs() - .context("Invalid address")? - .next() - .context("Could not lookup address") +fn parse_addr(s: Option<&str>) -> anyhow::Result { + s.with_context(|| "Missing address")? + .parse::() + .with_context(|| "Invalid address") } -fn parse_ip(s: Option<&String>) -> anyhow::Result { - s.context("Missing IP address")? +fn parse_ip(s: Option<&str>) -> anyhow::Result { + s.with_context(|| "Missing IP")? .parse::() - .context("Invalid IP address") + .with_context(|| "Invalid IP address") } -fn parse_private_key(s: &str) -> anyhow::Result { - let decoded = base64::decode(s).context("Failed to decode private key")?; - if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { - Ok(StaticSecret::from(bytes)) - } else { - bail!("Invalid private key") - } +fn parse_private_key(s: Option<&str>) -> anyhow::Result { + s.with_context(|| "Missing private key")? + .parse::() + .map_err(|e| anyhow::anyhow!("{}", e)) + .with_context(|| "Invalid private key") } -fn parse_public_key(s: Option<&String>) -> anyhow::Result { - let encoded = s.context("Missing public key")?; - let decoded = base64::decode(encoded).context("Failed to decode public key")?; - if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { - Ok(PublicKey::from(bytes)) - } else { - bail!("Invalid public key") - } +fn parse_public_key(s: Option<&str>) -> anyhow::Result { + s.with_context(|| "Missing public key")? + .parse::() + .map_err(|e| anyhow::anyhow!("{}", e)) + .with_context(|| "Invalid public key") } -fn parse_preshared_key(s: Option<&String>) -> anyhow::Result> { - if let Some(s) = s { - let decoded = base64::decode(s).context("Failed to decode preshared key")?; - if let Ok::<[u8; 32], _>(bytes) = decoded.try_into() { - Ok(Some(bytes)) - } else { - bail!("Invalid preshared key") - } - } else { - Ok(None) - } -} - -fn parse_keep_alive(s: Option<&String>) -> anyhow::Result> { +fn parse_keep_alive(s: Option<&str>) -> anyhow::Result> { if let Some(s) = s { let parsed: u16 = s.parse().with_context(|| { format!( @@ -346,369 +135,3 @@ fn parse_keep_alive(s: Option<&String>) -> anyhow::Result> { Ok(None) } } - -fn parse_mtu(s: Option<&String>) -> anyhow::Result { - s.context("Missing MTU")?.parse().context("Invalid MTU") -} - -#[cfg(unix)] -fn is_file_insecurely_readable(path: &String) -> Option<(bool, bool)> { - use std::fs::File; - use std::os::unix::fs::MetadataExt; - - let mode = File::open(path).ok()?.metadata().ok()?.mode(); - Some((mode & 0o40 > 0, mode & 0o4 > 0)) -} - -#[cfg(not(unix))] -fn is_file_insecurely_readable(_path: &String) -> Option<(bool, bool)> { - // No good way to determine permissions on non-Unix target - None -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct PortForwardConfig { - /// The source IP and port where the local server will run. - pub source: SocketAddr, - /// The destination IP and port to which traffic will be forwarded. - pub destination: SocketAddr, - /// The transport protocol to use for the port (Layer 4). - pub protocol: PortProtocol, - /// Whether this is a remote port forward. - pub remote: bool, -} - -impl PortForwardConfig { - /// Converts a string representation into `PortForwardConfig`. - /// - /// Sample formats: - /// - `127.0.0.1:8080:192.168.4.1:8081:TCP,UDP` - /// - `127.0.0.1:8080:192.168.4.1:8081:TCP` - /// - `0.0.0.0:8080:192.168.4.1:8081` - /// - `[::1]:8080:192.168.4.1:8081` - /// - `8080:192.168.4.1:8081` - /// - `8080:192.168.4.1:8081:TCP` - /// - `localhost:8080:192.168.4.1:8081:TCP` - /// - `localhost:8080:peer.intranet:8081:TCP` - /// - /// Implementation Notes: - /// - The format is formalized as `[src_host:]::[:PROTO1,PROTO2,...]` - /// - `src_host` is optional and defaults to `127.0.0.1`. - /// - `src_host` and `dst_host` may be specified as IPv4, IPv6, or a FQDN to be resolved by DNS. - /// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`. - /// - Any `u16` is accepted as `src_port` and `dst_port` - /// - Specifying protocols (`PROTO1,PROTO2,...`) is optional and defaults to `TCP`. Values must be separated by commas. - pub fn from_notation(s: &str, default_source: &str) -> anyhow::Result> { - mod parsers { - use nom::branch::alt; - use nom::bytes::complete::is_not; - use nom::character::complete::{alpha1, char, digit1}; - use nom::combinator::{complete, map, opt, success}; - use nom::error::ErrorKind; - use nom::multi::separated_list1; - use nom::sequence::{delimited, preceded, separated_pair, tuple}; - use nom::IResult; - - fn ipv6(s: &str) -> IResult<&str, &str> { - delimited(char('['), is_not("]"), char(']'))(s) - } - - fn ipv4_or_fqdn(s: &str) -> IResult<&str, &str> { - let s = is_not(":")(s)?; - if s.1.chars().all(|c| c.is_ascii_digit()) { - // If ipv4 or fqdn is all digits, it's not valid. - Err(nom::Err::Error(nom::error::ParseError::from_error_kind( - s.1, - ErrorKind::Fail, - ))) - } else { - Ok(s) - } - } - - fn port(s: &str) -> IResult<&str, &str> { - digit1(s) - } - - fn ip_or_fqdn(s: &str) -> IResult<&str, &str> { - alt((ipv6, ipv4_or_fqdn))(s) - } - - fn no_ip(s: &str) -> IResult<&str, Option<&str>> { - success(None)(s) - } - - fn src_addr(s: &str) -> IResult<&str, (Option<&str>, &str)> { - let with_ip = separated_pair(map(ip_or_fqdn, Some), char(':'), port); - let without_ip = tuple((no_ip, port)); - alt((with_ip, without_ip))(s) - } - - fn dst_addr(s: &str) -> IResult<&str, (&str, &str)> { - separated_pair(ip_or_fqdn, char(':'), port)(s) - } - - fn protocol(s: &str) -> IResult<&str, &str> { - alpha1(s) - } - - fn protocols(s: &str) -> IResult<&str, Option>> { - opt(preceded(char(':'), separated_list1(char(','), protocol)))(s) - } - - #[allow(clippy::type_complexity)] - pub fn port_forward( - s: &str, - ) -> IResult<&str, ((Option<&str>, &str), (), (&str, &str), Option>)> - { - complete(tuple(( - src_addr, - map(char(':'), |_| ()), - dst_addr, - protocols, - )))(s) - } - } - - // TODO: Could improve error management with custom errors, so that the messages are more helpful. - let (src_addr, _, dst_addr, protocols) = parsers::port_forward(s) - .map_err(|e| anyhow::anyhow!("Invalid port-forward definition: {}", e))? - .1; - - let source = ( - src_addr.0.unwrap_or(default_source), - src_addr.1.parse::().context("Invalid source port")?, - ) - .to_socket_addrs() - .context("Invalid source address")? - .next() - .context("Could not resolve source address")?; - - let destination = ( - dst_addr.0, - dst_addr.1.parse::().context("Invalid source port")?, - ) - .to_socket_addrs() // TODO: Pass this as given and use DNS config instead (issue #15) - .context("Invalid destination address")? - .next() - .context("Could not resolve destination address")?; - - // Parse protocols - let protocols = if let Some(protocols) = protocols { - let protocols: anyhow::Result> = - protocols.into_iter().map(PortProtocol::try_from).collect(); - protocols - } else { - Ok(vec![PortProtocol::Tcp]) - } - .context("Failed to parse protocols")?; - - // Returns an config for each protocol - Ok(protocols - .into_iter() - .map(|protocol| Self { - source, - destination, - protocol, - remote: false, - }) - .collect()) - } -} - -impl Display for PortForwardConfig { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.remote { - write!( - f, - "(remote){}:{}:{}", - self.source, self.destination, self.protocol - ) - } else { - write!(f, "{}:{}:{}", self.source, self.destination, self.protocol) - } - } -} - -/// Layer 7 protocols for ports. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum PortProtocol { - /// TCP - Tcp, - /// UDP - Udp, -} - -impl TryFrom<&str> for PortProtocol { - type Error = anyhow::Error; - - fn try_from(value: &str) -> anyhow::Result { - match value.to_uppercase().as_str() { - "TCP" => Ok(Self::Tcp), - "UDP" => Ok(Self::Udp), - _ => Err(anyhow::anyhow!("Invalid protocol specifier: {}", value)), - } - } -} - -impl Display for PortProtocol { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Tcp => "TCP", - Self::Udp => "UDP", - } - ) - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::*; - - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_1() { - assert_eq!( - PortForwardConfig::from_notation( - "192.168.0.1:8080:192.168.4.1:8081:TCP,UDP", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![ - PortForwardConfig { - source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }, - PortForwardConfig { - source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Udp, - remote: false, - } - ] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_2() { - assert_eq!( - PortForwardConfig::from_notation( - "192.168.0.1:8080:192.168.4.1:8081:TCP", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: SocketAddr::from_str("192.168.0.1:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_3() { - assert_eq!( - PortForwardConfig::from_notation( - "0.0.0.0:8080:192.168.4.1:8081", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: SocketAddr::from_str("0.0.0.0:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_4() { - assert_eq!( - PortForwardConfig::from_notation( - "[::1]:8080:192.168.4.1:8081", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: SocketAddr::from_str("[::1]:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_5() { - assert_eq!( - PortForwardConfig::from_notation("8080:192.168.4.1:8081", DEFAULT_PORT_FORWARD_SOURCE) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: SocketAddr::from_str("127.0.0.1:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_6() { - assert_eq!( - PortForwardConfig::from_notation( - "8080:192.168.4.1:8081:TCP", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: SocketAddr::from_str("127.0.0.1:8080").unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_7() { - assert_eq!( - PortForwardConfig::from_notation( - "localhost:8080:192.168.4.1:8081", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(), - destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } - /// Tests the parsing of `PortForwardConfig`. - #[test] - fn test_parse_port_forward_config_8() { - assert_eq!( - PortForwardConfig::from_notation( - "localhost:8080:localhost:8081:TCP", - DEFAULT_PORT_FORWARD_SOURCE - ) - .expect("Failed to parse"), - vec![PortForwardConfig { - source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(), - destination: "localhost:8081".to_socket_addrs().unwrap().next().unwrap(), - protocol: PortProtocol::Tcp, - remote: false, - }] - ); - } -} diff --git a/src/events.rs b/src/events.rs deleted file mode 100644 index d6582ce..0000000 --- a/src/events.rs +++ /dev/null @@ -1,190 +0,0 @@ -use bytes::Bytes; -use std::fmt::{Display, Formatter}; -use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::Arc; - -use crate::config::PortForwardConfig; -use crate::virtual_iface::VirtualPort; -use crate::PortProtocol; - -/// Events that go on the bus between the local server, smoltcp, and WireGuard. -#[derive(Debug, Clone)] -pub enum Event { - /// Dumb event with no data. - Dumb, - /// A new connection with the local server was initiated, and the given virtual port was assigned. - ClientConnectionInitiated(PortForwardConfig, VirtualPort), - /// A connection was dropped from the pool and should be closed in all interfaces. - ClientConnectionDropped(VirtualPort), - /// Data received by the local server that should be sent to the virtual server. - LocalData(PortForwardConfig, VirtualPort, Bytes), - /// Data received by the remote server that should be sent to the local client. - RemoteData(VirtualPort, Bytes), - /// IP packet received from the WireGuard tunnel that should be passed through the corresponding virtual device. - InboundInternetPacket(PortProtocol, Bytes), - /// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device. - OutboundInternetPacket(Bytes), - /// Notifies that a virtual device read an IP packet. - VirtualDeviceFed(PortProtocol), -} - -impl Display for Event { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Event::Dumb => { - write!(f, "Dumb{{}}") - } - Event::ClientConnectionInitiated(pf, vp) => { - write!(f, "ClientConnectionInitiated{{ pf={} vp={} }}", pf, vp) - } - Event::ClientConnectionDropped(vp) => { - write!(f, "ClientConnectionDropped{{ vp={} }}", vp) - } - Event::LocalData(pf, vp, data) => { - let size = data.len(); - write!(f, "LocalData{{ pf={} vp={} size={} }}", pf, vp, size) - } - Event::RemoteData(vp, data) => { - let size = data.len(); - write!(f, "RemoteData{{ vp={} size={} }}", vp, size) - } - Event::InboundInternetPacket(proto, data) => { - let size = data.len(); - write!( - f, - "InboundInternetPacket{{ proto={} size={} }}", - proto, size - ) - } - Event::OutboundInternetPacket(data) => { - let size = data.len(); - write!(f, "OutboundInternetPacket{{ size={} }}", size) - } - Event::VirtualDeviceFed(proto) => { - write!(f, "VirtualDeviceFed{{ proto={} }}", proto) - } - } - } -} - -#[derive(Clone)] -pub struct Bus { - counter: Arc, - bus: Arc>, -} - -impl Bus { - /// Creates a new event bus. - pub fn new() -> Self { - let (bus, _) = tokio::sync::broadcast::channel(1000); - let bus = Arc::new(bus); - let counter = Arc::new(AtomicU32::default()); - Self { bus, counter } - } - - /// Creates a new endpoint on the event bus. - pub fn new_endpoint(&self) -> BusEndpoint { - let id = self.counter.fetch_add(1, Ordering::Relaxed); - let tx = (*self.bus).clone(); - let rx = self.bus.subscribe(); - - let tx = BusSender { id, tx }; - BusEndpoint { id, tx, rx } - } -} - -impl Default for Bus { - fn default() -> Self { - Self::new() - } -} - -pub struct BusEndpoint { - id: u32, - tx: BusSender, - rx: tokio::sync::broadcast::Receiver<(u32, Event)>, -} - -impl BusEndpoint { - /// Sends the event on the bus. Note that the messages sent by this endpoint won't reach itself. - pub fn send(&self, event: Event) { - self.tx.send(event) - } - - /// Returns the unique sequential ID of this endpoint. - pub fn id(&self) -> u32 { - self.id - } - - /// Awaits the next `Event` on the bus to be read. - pub async fn recv(&mut self) -> Event { - loop { - match self.rx.recv().await { - Ok((id, event)) => { - if id == self.id { - // If the event was sent by this endpoint, it is skipped - continue; - } else { - return event; - } - } - Err(_) => { - error!("Failed to read event bus from endpoint #{}", self.id); - return futures::future::pending().await; - } - } - } - } - - /// Creates a new sender for this endpoint that can be cloned. - pub fn sender(&self) -> BusSender { - self.tx.clone() - } -} - -#[derive(Clone)] -pub struct BusSender { - id: u32, - tx: tokio::sync::broadcast::Sender<(u32, Event)>, -} - -impl BusSender { - /// Sends the event on the bus. Note that the messages sent by this endpoint won't reach itself. - pub fn send(&self, event: Event) { - trace!("#{} -> {}", self.id, event); - match self.tx.send((self.id, event)) { - Ok(_) => {} - Err(_) => error!("Failed to send event to bus from endpoint #{}", self.id), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_bus() { - let bus = Bus::new(); - - let mut endpoint_1 = bus.new_endpoint(); - let mut endpoint_2 = bus.new_endpoint(); - let mut endpoint_3 = bus.new_endpoint(); - - assert_eq!(endpoint_1.id(), 0); - assert_eq!(endpoint_2.id(), 1); - assert_eq!(endpoint_3.id(), 2); - - endpoint_1.send(Event::Dumb); - let recv_2 = endpoint_2.recv().await; - let recv_3 = endpoint_3.recv().await; - assert!(matches!(recv_2, Event::Dumb)); - assert!(matches!(recv_3, Event::Dumb)); - - endpoint_2.send(Event::Dumb); - let recv_1 = endpoint_1.recv().await; - let recv_3 = endpoint_3.recv().await; - assert!(matches!(recv_1, Event::Dumb)); - assert!(matches!(recv_3, Event::Dumb)); - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index a76fa18..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,122 +0,0 @@ -#[macro_use] -extern crate log; - -use std::sync::Arc; - -use anyhow::Context; - -use crate::config::{Config, PortProtocol}; -use crate::events::Bus; -use crate::tunnel::tcp::TcpPortPool; -use crate::tunnel::udp::UdpPortPool; -use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::tcp::TcpVirtualInterface; -use crate::virtual_iface::udp::UdpVirtualInterface; -use crate::virtual_iface::VirtualInterfacePoll; -use crate::wg::WireGuardTunnel; - -pub mod config; -pub mod events; -#[cfg(feature = "pcap")] -pub mod pcap; -pub mod tunnel; -pub mod virtual_device; -pub mod virtual_iface; -pub mod wg; - -/// Starts the onetun tunnels in separate tokio tasks. -/// -/// Note: This future completes immediately. -pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> { - // Initialize the port pool for each protocol - let tcp_port_pool = TcpPortPool::new(); - let udp_port_pool = UdpPortPool::new(); - - #[cfg(feature = "pcap")] - if let Some(pcap_file) = config.pcap_file.clone() { - // Start packet capture - let bus = bus.clone(); - tokio::spawn(async move { pcap::capture(pcap_file, bus).await }); - } - - let wg = WireGuardTunnel::new(&config, bus.clone()) - .await - .context("Failed to initialize WireGuard tunnel")?; - let wg = Arc::new(wg); - - { - // Start routine task for WireGuard - let wg = wg.clone(); - tokio::spawn(async move { wg.routine_task().await }); - } - - { - // Start consumption task for WireGuard - let wg = wg.clone(); - tokio::spawn(Box::pin(async move { wg.consume_task().await })); - } - - { - // Start production task for WireGuard - let wg = wg.clone(); - tokio::spawn(async move { wg.produce_task().await }); - } - - if config - .port_forwards - .iter() - .any(|pf| pf.protocol == PortProtocol::Tcp) - { - // TCP device - let bus = bus.clone(); - let device = - VirtualIpDevice::new(PortProtocol::Tcp, bus.clone(), config.max_transmission_unit); - - // Start TCP Virtual Interface - let port_forwards = config.port_forwards.clone(); - let iface = TcpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); - tokio::spawn(async move { iface.poll_loop(device).await }); - } - - if config - .port_forwards - .iter() - .any(|pf| pf.protocol == PortProtocol::Udp) - { - // UDP device - let bus = bus.clone(); - let device = - VirtualIpDevice::new(PortProtocol::Udp, bus.clone(), config.max_transmission_unit); - - // Start UDP Virtual Interface - let port_forwards = config.port_forwards.clone(); - let iface = UdpVirtualInterface::new(port_forwards, bus, config.source_peer_ip); - tokio::spawn(async move { iface.poll_loop(device).await }); - } - - { - let port_forwards = config.port_forwards; - let source_peer_ip = config.source_peer_ip; - - port_forwards - .into_iter() - .map(|pf| { - ( - pf, - wg.clone(), - tcp_port_pool.clone(), - udp_port_pool.clone(), - bus.clone(), - ) - }) - .for_each(move |(pf, wg, tcp_port_pool, udp_port_pool, bus)| { - tokio::spawn(async move { - tunnel::port_forward(pf, source_peer_ip, tcp_port_pool, udp_port_pool, wg, bus) - .await - .unwrap_or_else(|e| error!("Port-forward failed for {} : {}", pf, e)) - }); - }); - } - - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 4ff2954..9811071 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,389 @@ -#[cfg(feature = "bin")] #[macro_use] extern crate log; -#[cfg(feature = "bin")] +use std::net::{IpAddr, SocketAddr}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::Context; +use smoltcp::iface::InterfaceBuilder; +use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; +use smoltcp::wire::{IpAddress, IpCidr}; +use tokio::net::{TcpListener, TcpStream}; + +use crate::config::Config; +use crate::port_pool::PortPool; +use crate::virtual_device::VirtualIpDevice; +use crate::wg::WireGuardTunnel; + +pub mod config; +pub mod port_pool; +pub mod virtual_device; +pub mod wg; + +pub const MAX_PACKET: usize = 65536; + #[tokio::main] async fn main() -> anyhow::Result<()> { - use anyhow::Context; - use onetun::{config::Config, events::Bus}; - - let config = Config::from_args().context("Configuration has errors")?; + let config = Config::from_args().with_context(|| "Failed to read config")?; init_logger(&config)?; + let port_pool = Arc::new(PortPool::new()); - for warning in &config.warnings { - warn!("{}", warning); + let wg = WireGuardTunnel::new(&config, port_pool.clone()) + .await + .with_context(|| "Failed to initialize WireGuard tunnel")?; + let wg = Arc::new(wg); + + { + // Start routine task for WireGuard + let wg = wg.clone(); + tokio::spawn(async move { wg.routine_task().await }); } - let bus = Bus::default(); - onetun::start_tunnels(config, bus).await?; + { + // Start consumption task for WireGuard + let wg = wg.clone(); + tokio::spawn(async move { wg.consume_task().await }); + } - futures::future::pending().await + info!( + "Tunnelling [{}]->[{}] (via [{}] as peer {})", + &config.source_addr, &config.dest_addr, &config.endpoint_addr, &config.source_peer_ip + ); + + tcp_proxy_server( + config.source_addr, + config.source_peer_ip, + config.dest_addr, + port_pool.clone(), + wg, + ) + .await } -#[cfg(not(feature = "bin"))] -fn main() -> anyhow::Result<()> { - Err(anyhow::anyhow!("Binary compiled without 'bin' feature")) +/// Starts the server that listens on TCP connections. +async fn tcp_proxy_server( + listen_addr: SocketAddr, + source_peer_ip: IpAddr, + dest_addr: SocketAddr, + port_pool: Arc, + wg: Arc, +) -> anyhow::Result<()> { + let listener = TcpListener::bind(listen_addr) + .await + .with_context(|| "Failed to listen on TCP proxy server")?; + + loop { + let wg = wg.clone(); + let port_pool = port_pool.clone(); + let (socket, peer_addr) = listener + .accept() + .await + .with_context(|| "Failed to accept connection on TCP proxy server")?; + + // Assign a 'virtual port': this is a unique port number used to route IP packets + // received from the WireGuard tunnel. It is the port number that the virtual client will + // listen on. + let virtual_port = match port_pool.next() { + Ok(port) => port, + Err(e) => { + error!( + "Failed to assign virtual port number for connection [{}]: {:?}", + peer_addr, e + ); + continue; + } + }; + + info!("[{}] Incoming connection from {}", virtual_port, peer_addr); + + tokio::spawn(async move { + let port_pool = Arc::clone(&port_pool); + let result = + handle_tcp_proxy_connection(socket, virtual_port, source_peer_ip, dest_addr, wg) + .await; + + if let Err(e) = result { + error!( + "[{}] Connection dropped un-gracefully: {:?}", + virtual_port, e + ); + } else { + info!("[{}] Connection closed by client", virtual_port); + } + + // Release port when connection drops + port_pool.release(virtual_port); + }); + } } -#[cfg(feature = "bin")] -fn init_logger(config: &onetun::config::Config) -> anyhow::Result<()> { - use anyhow::Context; +/// Handles a new TCP connection with its assigned virtual port. +async fn handle_tcp_proxy_connection( + socket: TcpStream, + virtual_port: u16, + source_peer_ip: IpAddr, + dest_addr: SocketAddr, + wg: Arc, +) -> anyhow::Result<()> { + // Abort signal for stopping the Virtual Interface + let abort = Arc::new(AtomicBool::new(false)); - let mut builder = pretty_env_logger::formatted_timed_builder(); + // data_to_real_client_(tx/rx): This task reads the data from this mpsc channel to send back + // to the real client. + let (data_to_real_client_tx, mut data_to_real_client_rx) = tokio::sync::mpsc::channel(1_000); + + // data_to_real_server_(tx/rx): This task sends the data received from the real client to the + // virtual interface (virtual server socket). + let (data_to_virtual_server_tx, data_to_virtual_server_rx) = tokio::sync::mpsc::channel(1_000); + + // Spawn virtual interface + { + let abort = abort.clone(); + tokio::spawn(async move { + virtual_tcp_interface( + virtual_port, + source_peer_ip, + dest_addr, + wg, + abort, + data_to_real_client_tx, + data_to_virtual_server_rx, + ) + .await + }); + } + + loop { + if abort.load(Ordering::Relaxed) { + break; + } + + tokio::select! { + readable_result = socket.readable() => { + match readable_result { + Ok(_) => { + let mut buffer = vec![]; + match socket.try_read_buf(&mut buffer) { + Ok(size) if size > 0 => { + let data = &buffer[..size]; + debug!( + "[{}] Read {} bytes of TCP data from real client", + virtual_port, size + ); + if let Err(e) = data_to_virtual_server_tx.send(data.to_vec()).await { + error!( + "[{}] Failed to dispatch data to virtual interface: {:?}", + virtual_port, e + ); + } + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + continue; + } + Err(e) => { + error!( + "[{}] Failed to read from client TCP socket: {:?}", + virtual_port, e + ); + break; + } + _ => { + break; + } + } + } + Err(e) => { + error!("[{}] Failed to check if readable: {:?}", virtual_port, e); + break; + } + } + } + data_recv_result = data_to_real_client_rx.recv() => { + match data_recv_result { + Some(data) => match socket.try_write(&data) { + Ok(size) => { + debug!( + "[{}] Wrote {} bytes of TCP data to real client", + virtual_port, size + ); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + continue; + } + Err(e) => { + error!( + "[{}] Failed to write to client TCP socket: {:?}", + virtual_port, e + ); + } + }, + None => continue, + } + } + } + } + + trace!("[{}] TCP socket handler task terminated", virtual_port); + abort.store(true, Ordering::Relaxed); + Ok(()) +} + +async fn virtual_tcp_interface( + virtual_port: u16, + source_peer_ip: IpAddr, + dest_addr: SocketAddr, + wg: Arc, + abort: Arc, + data_to_real_client_tx: tokio::sync::mpsc::Sender>, + mut data_to_virtual_server_rx: tokio::sync::mpsc::Receiver>, +) -> anyhow::Result<()> { + // Create a device and interface to simulate IP packets + // In essence: + // * TCP packets received from the 'real' client are 'sent' to the 'virtual server' via the 'virtual client' + // * Those TCP packets generate IP packets, which are captured from the interface and sent to the WireGuardTunnel + // * IP packets received by the WireGuardTunnel (from the endpoint) are fed into this 'virtual interface' + // * The interface processes those IP packets and routes them to the 'virtual client' (the rest is discarded) + // * The TCP data read by the 'virtual client' is sent to the 'real' TCP client + + // Consumer for IP packets to send through the virtual interface + // Initialize the interface + let device = VirtualIpDevice::new(wg); + let mut virtual_interface = InterfaceBuilder::new(device) + .ip_addrs([ + // Interface handles IP packets for the sender and recipient + IpCidr::new(IpAddress::from(source_peer_ip), 32), + IpCidr::new(IpAddress::from(dest_addr.ip()), 32), + ]) + .any_ip(true) + .finalize(); + + // Server socket: this is a placeholder for the interface to route new connections to. + // TODO: Determine if we even need buffers here. + let server_socket: anyhow::Result = { + static mut TCP_SERVER_RX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; + static mut TCP_SERVER_TX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; + let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + + socket + .listen((IpAddress::from(dest_addr.ip()), dest_addr.port())) + .with_context(|| "Virtual server socket failed to listen")?; + + Ok(socket) + }; + + let client_socket: anyhow::Result = { + static mut TCP_SERVER_RX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; + static mut TCP_SERVER_TX_DATA: [u8; MAX_PACKET] = [0; MAX_PACKET]; + let tcp_rx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = TcpSocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + let mut socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + + socket + .connect( + (IpAddress::from(dest_addr.ip()), dest_addr.port()), + (IpAddress::from(source_peer_ip), virtual_port), + ) + .with_context(|| "Virtual server socket failed to listen")?; + + Ok(socket) + }; + + // Socket set: there are always 2 sockets: 1 virtual client and 1 virtual server. + let mut socket_set_entries: [_; 2] = Default::default(); + let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); + let _server_handle = socket_set.add(server_socket?); + let client_handle = socket_set.add(client_socket?); + + let mut graceful_shutdown = false; + + loop { + let loop_start = smoltcp::time::Instant::now(); + let forceful_shutdown = abort.load(Ordering::Relaxed); + + if forceful_shutdown { + // Un-graceful shutdown: sends a RST packet. + trace!( + "[{}] Forcefully shutting down virtual interface", + virtual_port + ); + let mut client_socket = socket_set.get::(client_handle); + client_socket.abort(); + } + + match virtual_interface.poll(&mut socket_set, loop_start) { + Ok(processed) if processed => { + trace!( + "[{}] Virtual interface polled some packets to be processed", + virtual_port + ); + } + Err(e) => { + error!("[{}] Virtual interface poll error: {:?}", virtual_port, e); + } + _ => {} + } + + { + let mut client_socket = socket_set.get::(client_handle); + if client_socket.can_recv() { + match client_socket.recv(|buffer| (buffer.len(), buffer.to_vec())) { + Ok(data) => { + // Send it to the real client + if let Err(e) = data_to_real_client_tx.send(data).await { + error!("[{}] Failed to dispatch data from virtual client to real client: {:?}", virtual_port, e); + } + } + Err(e) => { + error!( + "[{}] Failed to read from virtual client socket: {:?}", + virtual_port, e + ); + } + } + } + if client_socket.can_send() { + // Check if there is anything to send + if let Ok(data) = data_to_virtual_server_rx.try_recv() { + if let Err(e) = client_socket.send_slice(&data) { + error!( + "[{}] Failed to send slice via virtual client socket: {:?}", + virtual_port, e + ); + } + } + } + if !graceful_shutdown && !forceful_shutdown && !client_socket.is_active() { + // Graceful shutdown + client_socket.close(); + trace!( + "[{}] Gracefully shutting down virtual interface", + virtual_port + ); + // We don't break the loop right away so that the FIN segment can be sent in the next poll. + graceful_shutdown = true; + continue; + } + } + + if graceful_shutdown || forceful_shutdown { + break; + } + + tokio::time::sleep(Duration::from_millis(1)).await; + } + trace!("[{}] Virtual interface task terminated", virtual_port); + abort.store(true, Ordering::Relaxed); + Ok(()) +} + +fn init_logger(config: &Config) -> anyhow::Result<()> { + let mut builder = pretty_env_logger::formatted_builder(); builder.parse_filters(&config.log); - builder.try_init().context("Failed to initialize logger") + builder + .try_init() + .with_context(|| "Failed to initialize logger") } diff --git a/src/pcap.rs b/src/pcap.rs deleted file mode 100644 index 487fbe0..0000000 --- a/src/pcap.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::events::Event; -use crate::Bus; -use anyhow::Context; -use smoltcp::time::Instant; -use tokio::fs::File; -use tokio::io::{AsyncWriteExt, BufWriter}; - -struct Pcap { - writer: BufWriter, -} - -/// libpcap file writer -/// This is mostly taken from `smoltcp`, but rewritten to be async. -impl Pcap { - async fn flush(&mut self) -> anyhow::Result<()> { - self.writer - .flush() - .await - .context("Failed to flush pcap writer") - } - - async fn write(&mut self, data: &[u8]) -> anyhow::Result { - self.writer - .write(data) - .await - .with_context(|| format!("Failed to write {} bytes to pcap writer", data.len())) - } - - async fn write_u16(&mut self, value: u16) -> anyhow::Result<()> { - self.writer - .write_u16(value) - .await - .context("Failed to write u16 to pcap writer") - } - - async fn write_u32(&mut self, value: u32) -> anyhow::Result<()> { - self.writer - .write_u32(value) - .await - .context("Failed to write u32 to pcap writer") - } - - async fn global_header(&mut self) -> anyhow::Result<()> { - self.write_u32(0xa1b2c3d4).await?; // magic number - self.write_u16(2).await?; // major version - self.write_u16(4).await?; // minor version - self.write_u32(0).await?; // timezone (= UTC) - self.write_u32(0).await?; // accuracy (not used) - self.write_u32(65535).await?; // maximum packet length - self.write_u32(101).await?; // link-layer header type (101 = IP) - self.flush().await - } - - async fn packet_header(&mut self, timestamp: Instant, length: usize) -> anyhow::Result<()> { - assert!(length <= 65535); - - self.write_u32(timestamp.secs() as u32).await?; // timestamp seconds - self.write_u32(timestamp.micros() as u32).await?; // timestamp microseconds - self.write_u32(length as u32).await?; // captured length - self.write_u32(length as u32).await?; // original length - Ok(()) - } - - async fn packet(&mut self, timestamp: Instant, packet: &[u8]) -> anyhow::Result<()> { - self.packet_header(timestamp, packet.len()) - .await - .context("Failed to write packet header to pcap writer")?; - self.write(packet) - .await - .context("Failed to write packet to pcap writer")?; - self.writer - .flush() - .await - .context("Failed to flush pcap writer")?; - self.flush().await - } -} - -/// Listens on the event bus for IP packets sent from and to the WireGuard tunnel. -pub async fn capture(pcap_file: String, bus: Bus) -> anyhow::Result<()> { - let mut endpoint = bus.new_endpoint(); - let file = File::create(&pcap_file) - .await - .context("Failed to create pcap file")?; - let writer = BufWriter::new(file); - - let mut writer = Pcap { writer }; - writer - .global_header() - .await - .context("Failed to write global header to pcap writer")?; - - info!("Capturing WireGuard IP packets to {}", &pcap_file); - loop { - match endpoint.recv().await { - Event::InboundInternetPacket(_proto, ip) => { - let instant = Instant::now(); - writer - .packet(instant, &ip) - .await - .context("Failed to write inbound IP packet to pcap writer")?; - } - Event::OutboundInternetPacket(ip) => { - let instant = Instant::now(); - writer - .packet(instant, &ip) - .await - .context("Failed to write output IP packet to pcap writer")?; - } - _ => {} - } - } -} diff --git a/src/port_pool.rs b/src/port_pool.rs new file mode 100644 index 0000000..771c7a9 --- /dev/null +++ b/src/port_pool.rs @@ -0,0 +1,58 @@ +use std::ops::Range; + +use anyhow::Context; + +const MIN_PORT: u16 = 32768; +const MAX_PORT: u16 = 60999; +const PORT_RANGE: Range = MIN_PORT..MAX_PORT; + +/// A pool of virtual ports available. +/// This structure is thread-safe and lock-free; you can use it safely in an `Arc`. +pub struct PortPool { + /// Remaining ports + inner: lockfree::queue::Queue, + /// Ports in use + taken: lockfree::set::Set, +} + +impl Default for PortPool { + fn default() -> Self { + Self::new() + } +} + +impl PortPool { + /// Initializes a new pool of virtual ports. + pub fn new() -> Self { + let inner = lockfree::queue::Queue::default(); + PORT_RANGE.for_each(|p| inner.push(p) as ()); + Self { + inner, + taken: lockfree::set::Set::new(), + } + } + + /// Requests a free port from the pool. An error is returned if none is available (exhaused max capacity). + pub fn next(&self) -> anyhow::Result { + let port = self + .inner + .pop() + .with_context(|| "Virtual port pool is exhausted")?; + self.taken + .insert(port) + .ok() + .with_context(|| "Failed to insert taken")?; + Ok(port) + } + + /// Releases a port back into the pool. + pub fn release(&self, port: u16) { + self.inner.push(port); + self.taken.remove(&port); + } + + /// Whether the given port is in use by a virtual interface. + pub fn is_in_use(&self, port: u16) -> bool { + self.taken.contains(&port) + } +} diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs deleted file mode 100644 index eadf8b0..0000000 --- a/src/tunnel/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::net::IpAddr; -use std::sync::Arc; - -use crate::config::{PortForwardConfig, PortProtocol}; -use crate::events::Bus; -use crate::tunnel::tcp::TcpPortPool; -use crate::tunnel::udp::UdpPortPool; -use crate::wg::WireGuardTunnel; - -pub mod tcp; -pub mod udp; - -pub async fn port_forward( - port_forward: PortForwardConfig, - source_peer_ip: IpAddr, - tcp_port_pool: TcpPortPool, - udp_port_pool: UdpPortPool, - wg: Arc, - bus: Bus, -) -> anyhow::Result<()> { - info!( - "Tunneling {} [{}]->[{}] (via [{}] as peer {})", - port_forward.protocol, - port_forward.source, - port_forward.destination, - &wg.endpoint, - source_peer_ip - ); - - match port_forward.protocol { - PortProtocol::Tcp => tcp::tcp_proxy_server(port_forward, tcp_port_pool, bus).await, - PortProtocol::Udp => udp::udp_proxy_server(port_forward, udp_port_pool, bus).await, - } -} diff --git a/src/tunnel/tcp.rs b/src/tunnel/tcp.rs deleted file mode 100644 index 47b0197..0000000 --- a/src/tunnel/tcp.rs +++ /dev/null @@ -1,211 +0,0 @@ -use std::collections::VecDeque; -use std::ops::Range; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::Context; -use bytes::BytesMut; -use rand::seq::SliceRandom; -use rand::thread_rng; -use tokio::io::AsyncWriteExt; -use tokio::net::{TcpListener, TcpStream}; - -use crate::config::{PortForwardConfig, PortProtocol}; -use crate::events::{Bus, Event}; -use crate::virtual_iface::VirtualPort; - -const MAX_PACKET: usize = 65536; -const MIN_PORT: u16 = 1000; -const MAX_PORT: u16 = 60999; -const PORT_RANGE: Range = MIN_PORT..MAX_PORT; - -/// Starts the server that listens on TCP connections. -pub async fn tcp_proxy_server( - port_forward: PortForwardConfig, - port_pool: TcpPortPool, - bus: Bus, -) -> anyhow::Result<()> { - let listener = TcpListener::bind(port_forward.source) - .await - .context("Failed to listen on TCP proxy server")?; - - loop { - let port_pool = port_pool.clone(); - let (socket, peer_addr) = listener - .accept() - .await - .context("Failed to accept connection on TCP proxy server")?; - - // Assign a 'virtual port': this is a unique port number used to route IP packets - // received from the WireGuard tunnel. It is the port number that the virtual client will - // listen on. - let virtual_port = match port_pool.next().await { - Ok(port) => port, - Err(e) => { - error!( - "Failed to assign virtual port number for connection [{}]: {:?}", - peer_addr, e - ); - continue; - } - }; - - info!("[{}] Incoming connection from {}", virtual_port, peer_addr); - - let bus = bus.clone(); - tokio::spawn(async move { - let port_pool = port_pool.clone(); - let result = handle_tcp_proxy_connection(socket, virtual_port, port_forward, bus).await; - - if let Err(e) = result { - error!( - "[{}] Connection dropped un-gracefully: {:?}", - virtual_port, e - ); - } else { - info!("[{}] Connection closed by client", virtual_port); - } - - tokio::time::sleep(Duration::from_millis(100)).await; // Make sure the other tasks have time to process the event - port_pool.release(virtual_port).await; - }); - } -} - -/// Handles a new TCP connection with its assigned virtual port. -async fn handle_tcp_proxy_connection( - mut socket: TcpStream, - virtual_port: VirtualPort, - port_forward: PortForwardConfig, - bus: Bus, -) -> anyhow::Result<()> { - let mut endpoint = bus.new_endpoint(); - endpoint.send(Event::ClientConnectionInitiated(port_forward, virtual_port)); - - let mut buffer = BytesMut::with_capacity(MAX_PACKET); - loop { - tokio::select! { - readable_result = socket.readable() => { - match readable_result { - Ok(_) => { - match socket.try_read_buf(&mut buffer) { - Ok(size) if size > 0 => { - let data = Vec::from(&buffer[..size]); - endpoint.send(Event::LocalData(port_forward, virtual_port, data.into())); - // Reset buffer - buffer.clear(); - } - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - continue; - } - Err(e) => { - error!( - "[{}] Failed to read from client TCP socket: {:?}", - virtual_port, e - ); - break; - } - _ => { - break; - } - } - } - Err(e) => { - error!("[{}] Failed to check if readable: {:?}", virtual_port, e); - break; - } - } - } - event = endpoint.recv() => { - match event { - Event::ClientConnectionDropped(e_vp) if e_vp == virtual_port => { - // This connection is supposed to be closed, stop the task. - break; - } - Event::RemoteData(e_vp, data) if e_vp == virtual_port => { - // Have remote data to send to the local client - if let Err(e) = socket.writable().await { - error!("[{}] Failed to check if writable: {:?}", virtual_port, e); - } - let expected = data.len(); - let mut sent = 0; - loop { - if sent >= expected { - break; - } - match socket.write(&data[sent..expected]).await { - Ok(written) => { - debug!("[{}] Sent {} (expected {}) bytes to local client", virtual_port, written, expected); - sent += written; - if sent < expected { - debug!("[{}] Will try to resend remaining {} bytes to local client", virtual_port, (expected - written)); - } - }, - Err(e) => { - error!("[{}] Failed to send {} bytes to local client: {:?}", virtual_port, expected, e); - break; - } - } - } - } - _ => {} - } - } - } - } - - // Notify other endpoints that this task has closed and no more data is to be sent to the local client - endpoint.send(Event::ClientConnectionDropped(virtual_port)); - - Ok(()) -} - -/// A pool of virtual ports available for TCP connections. -#[derive(Clone)] -pub struct TcpPortPool { - inner: Arc>, -} - -impl Default for TcpPortPool { - fn default() -> Self { - Self::new() - } -} - -impl TcpPortPool { - /// Initializes a new pool of virtual ports. - pub fn new() -> Self { - let mut inner = TcpPortPoolInner::default(); - let mut ports: Vec = PORT_RANGE.collect(); - ports.shuffle(&mut thread_rng()); - ports - .into_iter() - .for_each(|p| inner.queue.push_back(p) as ()); - Self { - inner: Arc::new(tokio::sync::RwLock::new(inner)), - } - } - - /// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity). - pub async fn next(&self) -> anyhow::Result { - let mut inner = self.inner.write().await; - let port = inner - .queue - .pop_front() - .context("TCP virtual port pool is exhausted")?; - Ok(VirtualPort::new(port, PortProtocol::Tcp)) - } - - /// Releases a port back into the pool. - pub async fn release(&self, port: VirtualPort) { - let mut inner = self.inner.write().await; - inner.queue.push_back(port.num()); - } -} - -/// Non thread-safe inner logic for TCP port pool. -#[derive(Debug, Default)] -struct TcpPortPoolInner { - /// Remaining ports in the pool. - queue: VecDeque, -} diff --git a/src/tunnel/udp.rs b/src/tunnel/udp.rs deleted file mode 100644 index ab52dc7..0000000 --- a/src/tunnel/udp.rs +++ /dev/null @@ -1,257 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::net::{IpAddr, SocketAddr}; -use std::ops::Range; -use std::sync::Arc; -use std::time::Instant; - -use anyhow::Context; -use bytes::Bytes; -use priority_queue::double_priority_queue::DoublePriorityQueue; -use rand::seq::SliceRandom; -use rand::thread_rng; -use tokio::net::UdpSocket; - -use crate::config::{PortForwardConfig, PortProtocol}; -use crate::events::{Bus, Event}; -use crate::virtual_iface::VirtualPort; - -const MAX_PACKET: usize = 65536; -const MIN_PORT: u16 = 1000; -const MAX_PORT: u16 = 60999; -const PORT_RANGE: Range = MIN_PORT..MAX_PORT; - -/// How long to keep the UDP peer address assigned to its virtual specified port, in seconds. -/// TODO: Make this configurable by the CLI -const UDP_TIMEOUT_SECONDS: u64 = 60; - -/// To prevent port-flooding, we set a limit on the amount of open ports per IP address. -/// TODO: Make this configurable by the CLI -const PORTS_PER_IP: usize = 100; - -/// Starts the server that listens on UDP datagrams. -pub async fn udp_proxy_server( - port_forward: PortForwardConfig, - port_pool: UdpPortPool, - bus: Bus, -) -> anyhow::Result<()> { - let mut endpoint = bus.new_endpoint(); - let socket = UdpSocket::bind(port_forward.source) - .await - .context("Failed to bind on UDP proxy address")?; - - let mut buffer = [0u8; MAX_PACKET]; - loop { - tokio::select! { - to_send_result = next_udp_datagram(&socket, &mut buffer, port_pool.clone()) => { - match to_send_result { - Ok(Some((port, data))) => { - endpoint.send(Event::LocalData(port_forward, port, data)); - } - Ok(None) => { - continue; - } - Err(e) => { - error!( - "Failed to read from client UDP socket: {:?}", - e - ); - break; - } - } - } - event = endpoint.recv() => { - if let Event::RemoteData(virtual_port, data) = event { - if let Some(peer) = port_pool.get_peer_addr(virtual_port).await { - // Have remote data to send to the local client - if let Err(e) = socket.writable().await { - error!("[{}] Failed to check if writable: {:?}", virtual_port, e); - } - let expected = data.len(); - let mut sent = 0; - loop { - if sent >= expected { - break; - } - match socket.send_to(&data[sent..expected], peer).await { - Ok(written) => { - debug!("[{}] Sent {} (expected {}) bytes to local client", virtual_port, written, expected); - sent += written; - if sent < expected { - debug!("[{}] Will try to resend remaining {} bytes to local client", virtual_port, (expected - written)); - } - }, - Err(e) => { - error!("[{}] Failed to send {} bytes to local client: {:?}", virtual_port, expected, e); - break; - } - } - } - port_pool.update_last_transmit(virtual_port).await; - } - } - } - } - } - Ok(()) -} - -async fn next_udp_datagram( - socket: &UdpSocket, - buffer: &mut [u8], - port_pool: UdpPortPool, -) -> anyhow::Result> { - let (size, peer_addr) = socket - .recv_from(buffer) - .await - .context("Failed to accept incoming UDP datagram")?; - - // Assign a 'virtual port': this is a unique port number used to route IP packets - // received from the WireGuard tunnel. It is the port number that the virtual client will - // listen on. - let port = match port_pool.next(peer_addr).await { - Ok(port) => port, - Err(e) => { - error!( - "Failed to assign virtual port number for UDP datagram from [{}]: {:?}", - peer_addr, e - ); - return Ok(None); - } - }; - - debug!( - "[{}] Received datagram of {} bytes from {}", - port, size, peer_addr - ); - - port_pool.update_last_transmit(port).await; - - let data = buffer[..size].to_vec(); - Ok(Some((port, data.into()))) -} - -/// A pool of virtual ports available for TCP connections. -#[derive(Clone)] -pub struct UdpPortPool { - inner: Arc>, -} - -impl Default for UdpPortPool { - fn default() -> Self { - Self::new() - } -} - -impl UdpPortPool { - /// Initializes a new pool of virtual ports. - pub fn new() -> Self { - let mut inner = UdpPortPoolInner::default(); - let mut ports: Vec = PORT_RANGE.collect(); - ports.shuffle(&mut thread_rng()); - ports - .into_iter() - .for_each(|p| inner.queue.push_back(p) as ()); - Self { - inner: Arc::new(tokio::sync::RwLock::new(inner)), - } - } - - /// Requests a free port from the pool. An error is returned if none is available (exhausted max capacity). - pub async fn next(&self, peer_addr: SocketAddr) -> anyhow::Result { - // A port found to be reused. This is outside of the block because the read lock cannot be upgraded to a write lock. - let mut port_reuse: Option = None; - - { - let inner = self.inner.read().await; - if let Some(port) = inner.port_by_peer_addr.get(&peer_addr) { - return Ok(VirtualPort::new(*port, PortProtocol::Udp)); - } - - // Count how many ports are being used by the peer IP - let peer_ip = peer_addr.ip(); - let peer_port_count = inner - .peer_port_usage - .get(&peer_ip) - .map(|v| v.len()) - .unwrap_or_default(); - - if peer_port_count >= PORTS_PER_IP { - // Return least recently used port in this IP's pool - port_reuse = Some( - *(inner - .peer_port_usage - .get(&peer_ip) - .unwrap() - .peek_min() - .unwrap() - .0), - ); - warn!( - "Peer [{}] is re-using active virtual port {} due to self-exhaustion.", - peer_addr, - port_reuse.unwrap() - ); - } - } - - let mut inner = self.inner.write().await; - - let port = port_reuse - .or_else(|| inner.queue.pop_front()) - .or_else(|| { - // If there is no port to reuse, and the port pool is exhausted, take the last recently used port overall, - // as long as the last transmission exceeds the deadline - let last: (&u16, &Instant) = inner.port_usage.peek_min().unwrap(); - if Instant::now().duration_since(*last.1).as_secs() > UDP_TIMEOUT_SECONDS { - warn!( - "Peer [{}] is re-using inactive virtual port {} due to global exhaustion.", - peer_addr, last.0 - ); - Some(*last.0) - } else { - None - } - }) - .context("Virtual port pool is exhausted")?; - - inner.port_by_peer_addr.insert(peer_addr, port); - inner.peer_addr_by_port.insert(port, peer_addr); - Ok(VirtualPort::new(port, PortProtocol::Udp)) - } - - /// Notify that the given virtual port has received or transmitted a UDP datagram. - pub async fn update_last_transmit(&self, port: VirtualPort) { - let mut inner = self.inner.write().await; - if let Some(peer) = inner.peer_addr_by_port.get(&port.num()).copied() { - let pq: &mut DoublePriorityQueue = inner - .peer_port_usage - .entry(peer.ip()) - .or_insert_with(Default::default); - pq.push(port.num(), Instant::now()); - } - let pq: &mut DoublePriorityQueue = &mut inner.port_usage; - pq.push(port.num(), Instant::now()); - } - - pub async fn get_peer_addr(&self, port: VirtualPort) -> Option { - let inner = self.inner.read().await; - inner.peer_addr_by_port.get(&port.num()).copied() - } -} - -/// Non thread-safe inner logic for UDP port pool. -#[derive(Debug, Default)] -struct UdpPortPoolInner { - /// Remaining ports in the pool. - queue: VecDeque, - /// The port assigned by peer IP/port. This is used to lookup an existing virtual port - /// for an incoming UDP datagram. - port_by_peer_addr: HashMap, - /// The socket address assigned to a peer IP/port. This is used to send a UDP datagram to - /// the real peer address, given the virtual port. - peer_addr_by_port: HashMap, - /// Keeps an ordered map of the most recently used virtual ports by a peer (client) IP. - peer_port_usage: HashMap>, - /// Keeps an ordered map of the most recently used virtual ports in general. - port_usage: DoublePriorityQueue, -} diff --git a/src/virtual_device.rs b/src/virtual_device.rs index 28d8751..9692963 100644 --- a/src/virtual_device.rs +++ b/src/virtual_device.rs @@ -1,136 +1,94 @@ -use crate::config::PortProtocol; -use crate::events::{BusSender, Event}; -use crate::Bus; -use bytes::{BufMut, Bytes, BytesMut}; -use smoltcp::{ - phy::{DeviceCapabilities, Medium}, - time::Instant, -}; -use std::{ - collections::VecDeque, - sync::{Arc, Mutex}, -}; +use crate::wg::WireGuardTunnel; +use smoltcp::phy::{Device, DeviceCapabilities, Medium}; +use smoltcp::time::Instant; +use std::sync::Arc; -/// A virtual device that processes IP packets through smoltcp and WireGuard. +/// A virtual device that processes IP packets. IP packets received from the WireGuard endpoint +/// are made available to this device using a broadcast channel receiver. IP packets sent from this device +/// are asynchronously sent out to the WireGuard tunnel. pub struct VirtualIpDevice { - /// Max transmission unit (bytes) - max_transmission_unit: usize, - /// Channel receiver for received IP packets. - bus_sender: BusSender, - /// Local queue for packets received from the bus that need to go through the smoltcp interface. - process_queue: Arc>>, + /// Tunnel to send IP packets to. + wg: Arc, + /// Broadcast channel receiver for received IP packets. + ip_broadcast_rx: tokio::sync::broadcast::Receiver>, } impl VirtualIpDevice { - /// Initializes a new virtual IP device. - pub fn new(protocol: PortProtocol, bus: Bus, max_transmission_unit: usize) -> Self { - let mut bus_endpoint = bus.new_endpoint(); - let bus_sender = bus_endpoint.sender(); - let process_queue = Arc::new(Mutex::new(VecDeque::new())); - - { - let process_queue = process_queue.clone(); - tokio::spawn(async move { - loop { - match bus_endpoint.recv().await { - Event::InboundInternetPacket(ip_proto, data) if ip_proto == protocol => { - let mut queue = process_queue - .lock() - .expect("Failed to acquire process queue lock"); - queue.push_back(data); - bus_endpoint.send(Event::VirtualDeviceFed(ip_proto)); - } - _ => {} - } - } - }); - } + pub fn new(wg: Arc) -> Self { + let ip_broadcast_rx = wg.subscribe(); Self { - bus_sender, - process_queue, - max_transmission_unit, + wg, + ip_broadcast_rx, } } } -impl smoltcp::phy::Device for VirtualIpDevice { - type RxToken<'a> - = RxToken - where - Self: 'a; - type TxToken<'a> - = TxToken - where - Self: 'a; +impl<'a> Device<'a> for VirtualIpDevice { + type RxToken = RxToken; + type TxToken = TxToken; - fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - let next = { - let mut queue = self - .process_queue - .lock() - .expect("Failed to acquire process queue lock"); - queue.pop_front() - }; - match next { - Some(buffer) => Some(( - Self::RxToken { - buffer: { - let mut buf = BytesMut::new(); - buf.put(buffer); - buf - }, - }, + fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { + match self.ip_broadcast_rx.try_recv() { + Ok(buffer) => Some(( + Self::RxToken { buffer }, Self::TxToken { - sender: self.bus_sender.clone(), + wg: self.wg.clone(), }, )), - None => None, + Err(_) => None, } } - fn transmit(&mut self, _timestamp: Instant) -> Option> { + fn transmit(&'a mut self) -> Option { Some(TxToken { - sender: self.bus_sender.clone(), + wg: self.wg.clone(), }) } fn capabilities(&self) -> DeviceCapabilities { let mut cap = DeviceCapabilities::default(); cap.medium = Medium::Ip; - cap.max_transmission_unit = self.max_transmission_unit; + cap.max_transmission_unit = 65535; cap } } #[doc(hidden)] pub struct RxToken { - buffer: BytesMut, + buffer: Vec, } impl smoltcp::phy::RxToken for RxToken { - fn consume(self, f: F) -> R + fn consume(mut self, _timestamp: Instant, f: F) -> smoltcp::Result where - F: FnOnce(&[u8]) -> R, + F: FnOnce(&mut [u8]) -> smoltcp::Result, { - f(&self.buffer) + f(&mut self.buffer) } } #[doc(hidden)] pub struct TxToken { - sender: BusSender, + wg: Arc, } impl smoltcp::phy::TxToken for TxToken { - fn consume(self, len: usize, f: F) -> R + fn consume(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result where - F: FnOnce(&mut [u8]) -> R, + F: FnOnce(&mut [u8]) -> smoltcp::Result, { - let mut buffer = vec![0; len]; + let mut buffer = Vec::new(); + buffer.resize(len, 0); let result = f(&mut buffer); - self.sender - .send(Event::OutboundInternetPacket(buffer.into())); + tokio::spawn(async move { + match self.wg.send_ip_packet(&buffer).await { + Ok(_) => {} + Err(e) => { + error!("Failed to send IP packet to WireGuard endpoint: {:?}", e); + } + } + }); result } } diff --git a/src/virtual_iface/mod.rs b/src/virtual_iface/mod.rs deleted file mode 100644 index 4fd6f47..0000000 --- a/src/virtual_iface/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -pub mod tcp; -pub mod udp; - -use crate::config::PortProtocol; -use crate::VirtualIpDevice; -use async_trait::async_trait; -use std::fmt::{Display, Formatter}; - -#[async_trait] -pub trait VirtualInterfacePoll { - /// Initializes the virtual interface and processes incoming data to be dispatched - /// to the WireGuard tunnel and to the real client. - async fn poll_loop(mut self, device: VirtualIpDevice) -> anyhow::Result<()>; -} - -/// Virtual port. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct VirtualPort(u16, PortProtocol); - -impl VirtualPort { - /// Create a new `VirtualPort` instance, with the given port number and associated protocol. - pub fn new(port: u16, proto: PortProtocol) -> Self { - VirtualPort(port, proto) - } - - /// The port number - pub fn num(&self) -> u16 { - self.0 - } - - /// The protocol of this port. - pub fn proto(&self) -> PortProtocol { - self.1 - } -} - -impl From for u16 { - fn from(port: VirtualPort) -> Self { - port.num() - } -} - -impl From<&VirtualPort> for u16 { - fn from(port: &VirtualPort) -> Self { - port.num() - } -} - -impl From for PortProtocol { - fn from(port: VirtualPort) -> Self { - port.proto() - } -} - -impl From<&VirtualPort> for PortProtocol { - fn from(port: &VirtualPort) -> Self { - port.proto() - } -} - -impl Display for VirtualPort { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}:{}]", self.num(), self.proto()) - } -} diff --git a/src/virtual_iface/tcp.rs b/src/virtual_iface/tcp.rs deleted file mode 100644 index 3a3fd8d..0000000 --- a/src/virtual_iface/tcp.rs +++ /dev/null @@ -1,257 +0,0 @@ -use crate::config::{PortForwardConfig, PortProtocol}; -use crate::events::Event; -use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::Bus; -use anyhow::Context; -use async_trait::async_trait; -use bytes::Bytes; -use smoltcp::iface::PollResult; -use smoltcp::{ - iface::{Config, Interface, SocketHandle, SocketSet}, - socket::tcp, - time::Instant, - wire::{HardwareAddress, IpAddress, IpCidr, IpVersion}, -}; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - net::IpAddr, - time::Duration, -}; - -const MAX_PACKET: usize = 65536; - -/// A virtual interface for proxying Layer 7 data to Layer 3 packets, and vice-versa. -pub struct TcpVirtualInterface { - source_peer_ip: IpAddr, - port_forwards: Vec, - bus: Bus, - sockets: SocketSet<'static>, -} - -impl TcpVirtualInterface { - /// Initialize the parameters for a new virtual interface. - /// Use the `poll_loop()` future to start the virtual interface poll loop. - pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { - Self { - port_forwards: port_forwards - .into_iter() - .filter(|f| matches!(f.protocol, PortProtocol::Tcp)) - .collect(), - source_peer_ip, - bus, - sockets: SocketSet::new([]), - } - } - - fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { - static mut TCP_SERVER_RX_DATA: [u8; 0] = []; - static mut TCP_SERVER_TX_DATA: [u8; 0] = []; - - let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); - let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); - let mut socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - - socket - .listen(( - IpAddress::from(port_forward.destination.ip()), - port_forward.destination.port(), - )) - .context("Virtual server socket failed to listen")?; - - Ok(socket) - } - - fn new_client_socket() -> anyhow::Result> { - let rx_data = vec![0u8; MAX_PACKET]; - let tx_data = vec![0u8; MAX_PACKET]; - let tcp_rx_buffer = tcp::SocketBuffer::new(rx_data); - let tcp_tx_buffer = tcp::SocketBuffer::new(tx_data); - let socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - Ok(socket) - } - - fn addresses(&self) -> Vec { - let mut addresses = HashSet::new(); - addresses.insert(IpAddress::from(self.source_peer_ip)); - for config in self.port_forwards.iter() { - addresses.insert(IpAddress::from(config.destination.ip())); - } - addresses - .into_iter() - .map(|addr| IpCidr::new(addr, addr_length(&addr))) - .collect() - } -} - -#[async_trait] -impl VirtualInterfacePoll for TcpVirtualInterface { - async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { - // Create CIDR block for source peer IP + each port forward IP - let addresses = self.addresses(); - let config = Config::new(HardwareAddress::Ip); - - // Create virtual interface (contains smoltcp state machine) - let mut iface = Interface::new(config, &mut device, Instant::now()); - iface.update_ip_addrs(|ip_addrs| { - addresses.into_iter().for_each(|addr| { - ip_addrs - .push(addr) - .expect("maximum number of IPs in TCP interface reached"); - }); - }); - - // Create virtual server for each port forward - for port_forward in self.port_forwards.iter() { - let server_socket = TcpVirtualInterface::new_server_socket(*port_forward)?; - self.sockets.add(server_socket); - } - - // The next time to poll the interface. Can be None for instant poll. - let mut next_poll: Option = None; - - // Bus endpoint to read events - let mut endpoint = self.bus.new_endpoint(); - - // Maps virtual port to its client socket handle - let mut port_client_handle_map: HashMap = HashMap::new(); - - // Data packets to send from a virtual client - let mut send_queue: HashMap> = HashMap::new(); - - loop { - tokio::select! { - _ = match (next_poll, port_client_handle_map.len()) { - (None, 0) => tokio::time::sleep(Duration::MAX), - (None, _) => tokio::time::sleep(Duration::ZERO), - (Some(until), _) => tokio::time::sleep_until(until), - } => { - let loop_start = smoltcp::time::Instant::now(); - - // Find closed sockets - port_client_handle_map.retain(|virtual_port, client_handle| { - let client_socket = self.sockets.get_mut::(*client_handle); - if client_socket.state() == tcp::State::Closed { - endpoint.send(Event::ClientConnectionDropped(*virtual_port)); - send_queue.remove(virtual_port); - self.sockets.remove(*client_handle); - false - } else { - // Not closed, retain - true - } - }); - - if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged { - log::trace!("TCP virtual interface polled some packets to be processed"); - } - - for (virtual_port, client_handle) in port_client_handle_map.iter() { - let client_socket = self.sockets.get_mut::(*client_handle); - if client_socket.can_send() { - if let Some(send_queue) = send_queue.get_mut(virtual_port) { - let to_transfer = send_queue.pop_front(); - if let Some(to_transfer_slice) = to_transfer.as_deref() { - let total = to_transfer_slice.len(); - match client_socket.send_slice(to_transfer_slice) { - Ok(sent) => { - if sent < total { - // Sometimes only a subset is sent, so the rest needs to be sent on the next poll - let tx_extra = Vec::from(&to_transfer_slice[sent..total]); - send_queue.push_front(tx_extra.into()); - } - } - Err(e) => { - error!( - "Failed to send slice via virtual client socket: {:?}", e - ); - } - } - } else if client_socket.state() == tcp::State::CloseWait { - client_socket.close(); - } - } - } - if client_socket.can_recv() { - match client_socket.recv(|buffer| (buffer.len(), Bytes::from(buffer.to_vec()))) { - Ok(data) => { - debug!("[{}] Received {} bytes from virtual server", virtual_port, data.len()); - if !data.is_empty() { - endpoint.send(Event::RemoteData(*virtual_port, data)); - } - } - Err(e) => { - error!( - "Failed to read from virtual client socket: {:?}", e - ); - } - } - } - } - - // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) - next_poll = match iface.poll_delay(loop_start, &self.sockets) { - Some(smoltcp::time::Duration::ZERO) => None, - Some(delay) => { - trace!("TCP Virtual interface delayed next poll by {}", delay); - Some(tokio::time::Instant::now() + Duration::from_millis(delay.total_millis())) - }, - None => None, - }; - } - event = endpoint.recv() => { - match event { - Event::ClientConnectionInitiated(port_forward, virtual_port) => { - let client_socket = TcpVirtualInterface::new_client_socket()?; - let client_handle = self.sockets.add(client_socket); - - // Add handle to map - port_client_handle_map.insert(virtual_port, client_handle); - send_queue.insert(virtual_port, VecDeque::new()); - - let client_socket = self.sockets.get_mut::(client_handle); - let context = iface.context(); - - client_socket - .connect( - context, - ( - IpAddress::from(port_forward.destination.ip()), - port_forward.destination.port(), - ), - (IpAddress::from(self.source_peer_ip), virtual_port.num()), - ) - .context("Virtual server socket failed to listen")?; - - next_poll = None; - } - Event::ClientConnectionDropped(virtual_port) => { - if let Some(client_handle) = port_client_handle_map.get(&virtual_port) { - let client_socket = self.sockets.get_mut::(*client_handle); - client_socket.close(); - next_poll = None; - } - } - Event::LocalData(_, virtual_port, data) if send_queue.contains_key(&virtual_port) => { - if let Some(send_queue) = send_queue.get_mut(&virtual_port) { - send_queue.push_back(data); - next_poll = None; - } - } - Event::VirtualDeviceFed(PortProtocol::Tcp) => { - next_poll = None; - } - _ => {} - } - } - } - } - } -} - -const fn addr_length(addr: &IpAddress) -> u8 { - match addr.version() { - IpVersion::Ipv4 => 32, - IpVersion::Ipv6 => 128, - } -} diff --git a/src/virtual_iface/udp.rs b/src/virtual_iface/udp.rs deleted file mode 100644 index cb643c9..0000000 --- a/src/virtual_iface/udp.rs +++ /dev/null @@ -1,227 +0,0 @@ -use crate::config::PortForwardConfig; -use crate::events::Event; -use crate::virtual_device::VirtualIpDevice; -use crate::virtual_iface::{VirtualInterfacePoll, VirtualPort}; -use crate::{Bus, PortProtocol}; -use anyhow::Context; -use async_trait::async_trait; -use bytes::Bytes; -use smoltcp::iface::PollResult; -use smoltcp::{ - iface::{Config, Interface, SocketHandle, SocketSet}, - socket::udp::{self, UdpMetadata}, - time::Instant, - wire::{HardwareAddress, IpAddress, IpCidr, IpVersion}, -}; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - net::IpAddr, - time::Duration, -}; - -const MAX_PACKET: usize = 65536; - -pub struct UdpVirtualInterface { - source_peer_ip: IpAddr, - port_forwards: Vec, - bus: Bus, - sockets: SocketSet<'static>, -} - -impl UdpVirtualInterface { - /// Initialize the parameters for a new virtual interface. - /// Use the `poll_loop()` future to start the virtual interface poll loop. - pub fn new(port_forwards: Vec, bus: Bus, source_peer_ip: IpAddr) -> Self { - Self { - port_forwards: port_forwards - .into_iter() - .filter(|f| matches!(f.protocol, PortProtocol::Udp)) - .collect(), - source_peer_ip, - bus, - sockets: SocketSet::new([]), - } - } - - fn new_server_socket(port_forward: PortForwardConfig) -> anyhow::Result> { - static mut UDP_SERVER_RX_META: [udp::PacketMetadata; 0] = []; - static mut UDP_SERVER_RX_DATA: [u8; 0] = []; - static mut UDP_SERVER_TX_META: [udp::PacketMetadata; 0] = []; - static mut UDP_SERVER_TX_DATA: [u8; 0] = []; - let udp_rx_buffer = - udp::PacketBuffer::new(unsafe { &mut UDP_SERVER_RX_META[..] }, unsafe { - &mut UDP_SERVER_RX_DATA[..] - }); - let udp_tx_buffer = - udp::PacketBuffer::new(unsafe { &mut UDP_SERVER_TX_META[..] }, unsafe { - &mut UDP_SERVER_TX_DATA[..] - }); - let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); - socket - .bind(( - IpAddress::from(port_forward.destination.ip()), - port_forward.destination.port(), - )) - .context("UDP virtual server socket failed to bind")?; - Ok(socket) - } - - fn new_client_socket( - source_peer_ip: IpAddr, - client_port: VirtualPort, - ) -> anyhow::Result> { - let rx_meta = vec![udp::PacketMetadata::EMPTY; 10]; - let tx_meta = vec![udp::PacketMetadata::EMPTY; 10]; - let rx_data = vec![0u8; MAX_PACKET]; - let tx_data = vec![0u8; MAX_PACKET]; - let udp_rx_buffer = udp::PacketBuffer::new(rx_meta, rx_data); - let udp_tx_buffer = udp::PacketBuffer::new(tx_meta, tx_data); - let mut socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); - socket - .bind((IpAddress::from(source_peer_ip), client_port.num())) - .context("UDP virtual client failed to bind")?; - Ok(socket) - } - - fn addresses(&self) -> Vec { - let mut addresses = HashSet::new(); - addresses.insert(IpAddress::from(self.source_peer_ip)); - for config in self.port_forwards.iter() { - addresses.insert(IpAddress::from(config.destination.ip())); - } - addresses - .into_iter() - .map(|addr| IpCidr::new(addr, addr_length(&addr))) - .collect() - } -} - -#[async_trait] -impl VirtualInterfacePoll for UdpVirtualInterface { - async fn poll_loop(mut self, mut device: VirtualIpDevice) -> anyhow::Result<()> { - // Create CIDR block for source peer IP + each port forward IP - let addresses = self.addresses(); - let config = Config::new(HardwareAddress::Ip); - - // Create virtual interface (contains smoltcp state machine) - let mut iface = Interface::new(config, &mut device, Instant::now()); - iface.update_ip_addrs(|ip_addrs| { - addresses.into_iter().for_each(|addr| { - ip_addrs - .push(addr) - .expect("maximum number of IPs in UDP interface reached"); - }); - }); - - // Create virtual server for each port forward - for port_forward in self.port_forwards.iter() { - let server_socket = UdpVirtualInterface::new_server_socket(*port_forward)?; - self.sockets.add(server_socket); - } - - // The next time to poll the interface. Can be None for instant poll. - let mut next_poll: Option = None; - - // Bus endpoint to read events - let mut endpoint = self.bus.new_endpoint(); - - // Maps virtual port to its client socket handle - let mut port_client_handle_map: HashMap = HashMap::new(); - - // Data packets to send from a virtual client - let mut send_queue: HashMap> = - HashMap::new(); - - loop { - tokio::select! { - _ = match (next_poll, port_client_handle_map.len()) { - (None, 0) => tokio::time::sleep(Duration::MAX), - (None, _) => tokio::time::sleep(Duration::ZERO), - (Some(until), _) => tokio::time::sleep_until(until), - } => { - let loop_start = smoltcp::time::Instant::now(); - - if iface.poll(loop_start, &mut device, &mut self.sockets) == PollResult::SocketStateChanged { - log::trace!("UDP virtual interface polled some packets to be processed"); - } - - for (virtual_port, client_handle) in port_client_handle_map.iter() { - let client_socket = self.sockets.get_mut::(*client_handle); - if client_socket.can_send() { - if let Some(send_queue) = send_queue.get_mut(virtual_port) { - let to_transfer = send_queue.pop_front(); - if let Some((port_forward, data)) = to_transfer { - client_socket - .send_slice( - &data, - UdpMetadata::from(port_forward.destination), - ) - .unwrap_or_else(|e| { - error!( - "[{}] Failed to send data to virtual server: {:?}", - virtual_port, e - ); - }); - } - } - } - if client_socket.can_recv() { - match client_socket.recv() { - Ok((data, _peer)) => { - if !data.is_empty() { - endpoint.send(Event::RemoteData(*virtual_port, data.to_vec().into())); - } - } - Err(e) => { - error!( - "Failed to read from virtual client socket: {:?}", e - ); - } - } - } - } - - // The virtual interface determines the next time to poll (this is to reduce unnecessary polls) - next_poll = match iface.poll_delay(loop_start, &self.sockets) { - Some(smoltcp::time::Duration::ZERO) => None, - Some(delay) => { - trace!("UDP Virtual interface delayed next poll by {}", delay); - Some(tokio::time::Instant::now() + Duration::from_millis(delay.total_millis())) - }, - None => None, - }; - } - event = endpoint.recv() => { - match event { - Event::LocalData(port_forward, virtual_port, data) => { - if let Some(send_queue) = send_queue.get_mut(&virtual_port) { - // Client socket already exists - send_queue.push_back((port_forward, data)); - } else { - // Client socket does not exist - let client_socket = UdpVirtualInterface::new_client_socket(self.source_peer_ip, virtual_port)?; - let client_handle = self.sockets.add(client_socket); - - // Add handle to map - port_client_handle_map.insert(virtual_port, client_handle); - send_queue.insert(virtual_port, VecDeque::from(vec![(port_forward, data)])); - } - next_poll = None; - } - Event::VirtualDeviceFed(PortProtocol::Udp) => { - next_poll = None; - } - _ => {} - } - } - } - } - } -} - -const fn addr_length(addr: &IpAddress) -> u8 { - match addr.version() { - IpVersion::Ipv4 => 32, - IpVersion::Ipv6 => 128, - } -} diff --git a/src/wg.rs b/src/wg.rs index f114c43..4b88ef6 100644 --- a/src/wg.rs +++ b/src/wg.rs @@ -1,54 +1,63 @@ -use std::net::{IpAddr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::sync::Arc; use std::time::Duration; -use crate::Bus; use anyhow::Context; -use async_recursion::async_recursion; -use boringtun::noise::errors::WireGuardError; use boringtun::noise::{Tunn, TunnResult}; use log::Level; -use smoltcp::wire::{IpProtocol, IpVersion, Ipv4Packet, Ipv6Packet}; +use smoltcp::phy::ChecksumCapabilities; +use smoltcp::wire::{ + IpAddress, IpProtocol, IpVersion, Ipv4Packet, Ipv4Repr, Ipv6Packet, Ipv6Repr, TcpControl, + TcpPacket, TcpRepr, TcpSeqNumber, +}; use tokio::net::UdpSocket; -use tokio::sync::Mutex; -use crate::config::{Config, PortProtocol}; -use crate::events::Event; +use crate::config::Config; +use crate::port_pool::PortPool; +use crate::MAX_PACKET; -/// The capacity of the channel for received IP packets. -pub const DISPATCH_CAPACITY: usize = 1_000; -const MAX_PACKET: usize = 65536; +/// The capacity of the broadcast channel for received IP packets. +const BROADCAST_CAPACITY: usize = 1_000; /// A WireGuard tunnel. Encapsulates and decapsulates IP packets /// to be sent to and received from a remote UDP endpoint. /// This tunnel supports at most 1 peer IP at a time, but supports simultaneous ports. pub struct WireGuardTunnel { - pub(crate) source_peer_ip: IpAddr, + source_peer_ip: IpAddr, /// `boringtun` peer/tunnel implementation, used for crypto & WG protocol. - peer: Mutex>, + peer: Box, /// The UDP socket for the public WireGuard endpoint to connect to. udp: UdpSocket, /// The address of the public WireGuard endpoint (UDP). - pub(crate) endpoint: SocketAddr, - /// Event bus - bus: Bus, + endpoint: SocketAddr, + /// Broadcast sender for received IP packets. + ip_broadcast_tx: tokio::sync::broadcast::Sender>, + /// Placeholder so that the broadcaster doesn't close. + _ip_broadcast_rx: tokio::sync::broadcast::Receiver>, + /// Port pool. + port_pool: Arc, } impl WireGuardTunnel { /// Initialize a new WireGuard tunnel. - pub async fn new(config: &Config, bus: Bus) -> anyhow::Result { + pub async fn new(config: &Config, port_pool: Arc) -> anyhow::Result { let source_peer_ip = config.source_peer_ip; - let peer = Mutex::new(Box::new(Self::create_tunnel(config)?)); - let endpoint = config.endpoint_addr; - let udp = UdpSocket::bind(config.endpoint_bind_addr) + let peer = Self::create_tunnel(config)?; + let udp = UdpSocket::bind("0.0.0.0:0") .await - .context("Failed to create UDP socket for WireGuard connection")?; + .with_context(|| "Failed to create UDP socket for WireGuard connection")?; + let endpoint = config.endpoint_addr; + let (ip_broadcast_tx, ip_broadcast_rx) = + tokio::sync::broadcast::channel(BROADCAST_CAPACITY); Ok(Self { source_peer_ip, peer, udp, endpoint, - bus, + ip_broadcast_tx, + _ip_broadcast_rx: ip_broadcast_rx, + port_pool, }) } @@ -56,16 +65,12 @@ impl WireGuardTunnel { pub async fn send_ip_packet(&self, packet: &[u8]) -> anyhow::Result<()> { trace_ip_packet("Sending IP packet", packet); let mut send_buf = [0u8; MAX_PACKET]; - let encapsulate_result = { - let mut peer = self.peer.lock().await; - peer.encapsulate(packet, &mut send_buf) - }; - match encapsulate_result { + match self.peer.encapsulate(packet, &mut send_buf) { TunnResult::WriteToNetwork(packet) => { self.udp .send_to(packet, self.endpoint) .await - .context("Failed to send encrypted IP packet to WireGuard endpoint.")?; + .with_context(|| "Failed to send encrypted IP packet to WireGuard endpoint.")?; debug!( "Sent {} bytes to WireGuard endpoint (encrypted IP packet)", packet.len() @@ -87,20 +92,9 @@ impl WireGuardTunnel { Ok(()) } - pub async fn produce_task(&self) -> ! { - trace!("Starting WireGuard production task"); - let mut endpoint = self.bus.new_endpoint(); - - loop { - if let Event::OutboundInternetPacket(data) = endpoint.recv().await { - match self.send_ip_packet(&data).await { - Ok(_) => {} - Err(e) => { - error!("{:?}", e); - } - } - } - } + /// Create a new receiver for broadcasted IP packets, received from the WireGuard endpoint. + pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver> { + self.ip_broadcast_tx.subscribe() } /// WireGuard Routine task. Handles Handshake, keep-alive, etc. @@ -109,62 +103,43 @@ impl WireGuardTunnel { loop { let mut send_buf = [0u8; MAX_PACKET]; - let tun_result = { self.peer.lock().await.update_timers(&mut send_buf) }; - self.handle_routine_tun_result(tun_result).await; + match self.peer.update_timers(&mut send_buf) { + TunnResult::WriteToNetwork(packet) => { + debug!( + "Sending routine packet of {} bytes to WireGuard endpoint", + packet.len() + ); + match self.udp.send_to(packet, self.endpoint).await { + Ok(_) => {} + Err(e) => { + error!( + "Failed to send routine packet to WireGuard endpoint: {:?}", + e + ); + } + }; + } + TunnResult::Err(e) => { + error!( + "Failed to prepare routine packet for WireGuard endpoint: {:?}", + e + ); + } + TunnResult::Done => { + // Sleep for a bit + tokio::time::sleep(Duration::from_millis(1)).await; + } + other => { + warn!("Unexpected WireGuard routine task state: {:?}", other); + } + } } } - #[async_recursion] - async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) -> () { - match result { - TunnResult::WriteToNetwork(packet) => { - debug!( - "Sending routine packet of {} bytes to WireGuard endpoint", - packet.len() - ); - match self.udp.send_to(packet, self.endpoint).await { - Ok(_) => {} - Err(e) => { - error!( - "Failed to send routine packet to WireGuard endpoint: {:?}", - e - ); - } - }; - } - TunnResult::Err(WireGuardError::ConnectionExpired) => { - warn!("Wireguard handshake has expired!"); - - let mut buf = vec![0u8; MAX_PACKET]; - let result = self - .peer - .lock() - .await - .format_handshake_initiation(&mut buf[..], false); - - self.handle_routine_tun_result(result).await - } - TunnResult::Err(e) => { - error!( - "Failed to prepare routine packet for WireGuard endpoint: {:?}", - e - ); - } - TunnResult::Done => { - // Sleep for a bit - tokio::time::sleep(Duration::from_millis(1)).await; - } - other => { - warn!("Unexpected WireGuard routine task state: {:?}", other); - } - }; - } - /// WireGuard consumption task. Receives encrypted packets from the WireGuard endpoint, - /// decapsulates them, and dispatches newly received IP packets. + /// decapsulates them, and broadcasts newly received IP packets. pub async fn consume_task(&self) -> ! { trace!("Starting WireGuard consumption task"); - let endpoint = self.bus.new_endpoint(); loop { let mut recv_buf = [0u8; MAX_PACKET]; @@ -181,11 +156,7 @@ impl WireGuardTunnel { }; let data = &recv_buf[..size]; - let decapsulate_result = { - let mut peer = self.peer.lock().await; - peer.decapsulate(None, data, &mut send_buf) - }; - match decapsulate_result { + match self.peer.decapsulate(None, data, &mut send_buf) { TunnResult::WriteToNetwork(packet) => { match self.udp.send_to(packet, self.endpoint).await { Ok(_) => {} @@ -194,10 +165,9 @@ impl WireGuardTunnel { continue; } }; - let mut peer = self.peer.lock().await; loop { let mut send_buf = [0u8; MAX_PACKET]; - match peer.decapsulate(None, &[], &mut send_buf) { + match self.peer.decapsulate(None, &[], &mut send_buf) { TunnResult::WriteToNetwork(packet) => { match self.udp.send_to(packet, self.endpoint).await { Ok(_) => {} @@ -222,8 +192,35 @@ impl WireGuardTunnel { // For debugging purposes: parse packet trace_ip_packet("Received IP packet", packet); - if let Some(proto) = self.route_protocol(packet) { - endpoint.send(Event::InboundInternetPacket(proto, packet.to_vec().into())); + match self.route_ip_packet(packet) { + RouteResult::Broadcast => { + // Broadcast IP packet + if self.ip_broadcast_tx.receiver_count() > 1 { + match self.ip_broadcast_tx.send(packet.to_vec()) { + Ok(n) => { + trace!( + "Broadcasted received IP packet to {} virtual interfaces", + n - 1 + ); + } + Err(e) => { + error!( + "Failed to broadcast received IP packet to recipients: {}", + e + ); + } + } + } + } + RouteResult::TcpReset(packet) => { + trace!("Resetting dead TCP connection after packet from WireGuard endpoint"); + self.send_ip_packet(&packet) + .await + .unwrap_or_else(|e| error!("Failed to sent TCP reset: {:?}", e)); + } + RouteResult::Drop => { + trace!("Dropped incoming IP packet from WireGuard endpoint"); + } } } _ => {} @@ -231,48 +228,175 @@ impl WireGuardTunnel { } } - fn create_tunnel(config: &Config) -> anyhow::Result { - let private = config.private_key.as_ref().clone(); - let public = *config.endpoint_public_key.as_ref(); - + fn create_tunnel(config: &Config) -> anyhow::Result> { Tunn::new( - private, - public, - config.preshared_key, + config.private_key.clone(), + config.endpoint_public_key.clone(), + None, config.keepalive_seconds, 0, None, ) .map_err(|s| anyhow::anyhow!("{}", s)) - .context("Failed to initialize boringtun Tunn") + .with_context(|| "Failed to initialize boringtun Tunn") } - /// Determine the inner protocol of the incoming IP packet (TCP/UDP). - fn route_protocol(&self, packet: &[u8]) -> Option { + /// Makes a decision on the handling of an incoming IP packet. + fn route_ip_packet(&self, packet: &[u8]) -> RouteResult { match IpVersion::of_packet(packet) { Ok(IpVersion::Ipv4) => Ipv4Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel - .filter(|packet| packet.dst_addr() == self.source_peer_ip) - .and_then(|packet| match packet.next_header() { - IpProtocol::Tcp => Some(PortProtocol::Tcp), - IpProtocol::Udp => Some(PortProtocol::Udp), - // Unrecognized protocol, so we cannot determine where to route - _ => None, - }), + .filter(|packet| Ipv4Addr::from(packet.dst_addr()) == self.source_peer_ip) + .map(|packet| match packet.protocol() { + IpProtocol::Tcp => Some( + self.route_tcp_segment( + packet.src_addr().into(), + packet.dst_addr().into(), + packet.payload(), + ) + // Note: Ipv4 drops invalid TCP packets when the specified protocol says that it should be TCP + .unwrap_or(RouteResult::Drop), + ), + // Unrecognized protocol, so we'll allow it. + _ => Some(RouteResult::Broadcast), + }) + .flatten() + .unwrap_or(RouteResult::Drop), Ok(IpVersion::Ipv6) => Ipv6Packet::new_checked(&packet) .ok() // Only care if the packet is destined for this tunnel - .filter(|packet| packet.dst_addr() == self.source_peer_ip) - .and_then(|packet| match packet.next_header() { - IpProtocol::Tcp => Some(PortProtocol::Tcp), - IpProtocol::Udp => Some(PortProtocol::Udp), - // Unrecognized protocol, so we cannot determine where to route - _ => None, - }), - _ => None, + .filter(|packet| Ipv6Addr::from(packet.dst_addr()) == self.source_peer_ip) + .map(|packet| { + self.route_tcp_segment( + packet.src_addr().into(), + packet.dst_addr().into(), + packet.payload(), + ) + // Note: Since Ipv6 doesn't inform us of the protocol at this layer, + // we should broadcast unrecognized packets. + .unwrap_or(RouteResult::Broadcast) + }) + .unwrap_or(RouteResult::Drop), + _ => RouteResult::Drop, } } + + /// Makes a decision on the handling of an incoming TCP segment. + /// When the given segment is an invalid TCP packet, it returns `None`. + fn route_tcp_segment( + &self, + src_addr: IpAddress, + dst_addr: IpAddress, + segment: &[u8], + ) -> Option { + TcpPacket::new_checked(segment).ok().map(|tcp| { + if self.port_pool.is_in_use(tcp.dst_port()) { + RouteResult::Broadcast + } else if tcp.rst() { + RouteResult::Drop + } else { + // Port is not in use, but it's a TCP packet so we'll craft a RST. + RouteResult::TcpReset(craft_tcp_rst_reply( + IpVersion::Ipv4, + src_addr, + tcp.src_port(), + dst_addr, + tcp.dst_port(), + tcp.ack_number(), + )) + } + }) + } +} + +/// Craft an IP packet containing a TCP RST segment, given an IP version, +/// source address (the one to reply to), destination address (the one the reply comes from), +/// and the ACK number received in the initiating TCP segment. +fn craft_tcp_rst_reply( + ip_version: IpVersion, + source_addr: IpAddress, + source_port: u16, + dest_addr: IpAddress, + dest_port: u16, + ack_number: TcpSeqNumber, +) -> Vec { + let tcp_repr = TcpRepr { + src_port: dest_port, + dst_port: source_port, + control: TcpControl::Rst, + seq_number: ack_number, + ack_number: None, + window_len: 0, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + payload: &[], + }; + + let mut tcp_buffer = vec![0u8; 20]; + let mut tcp_packet = &mut TcpPacket::new_unchecked(&mut tcp_buffer); + tcp_repr.emit( + &mut tcp_packet, + &dest_addr, + &source_addr, + &ChecksumCapabilities::default(), + ); + + let mut ip_buffer = vec![0u8; MAX_PACKET]; + + let (header_len, total_len) = match ip_version { + IpVersion::Ipv4 => { + let dest_addr = match dest_addr { + IpAddress::Ipv4(dest_addr) => dest_addr, + _ => panic!(), + }; + let source_addr = match source_addr { + IpAddress::Ipv4(source_addr) => source_addr, + _ => panic!(), + }; + + let mut ip_packet = &mut Ipv4Packet::new_unchecked(&mut ip_buffer); + let ip_repr = Ipv4Repr { + src_addr: dest_addr, + dst_addr: source_addr, + protocol: IpProtocol::Tcp, + payload_len: tcp_buffer.len(), + hop_limit: 64, + }; + ip_repr.emit(&mut ip_packet, &ChecksumCapabilities::default()); + ( + ip_packet.header_len() as usize, + ip_packet.total_len() as usize, + ) + } + IpVersion::Ipv6 => { + let dest_addr = match dest_addr { + IpAddress::Ipv6(dest_addr) => dest_addr, + _ => panic!(), + }; + let source_addr = match source_addr { + IpAddress::Ipv6(source_addr) => source_addr, + _ => panic!(), + }; + let mut ip_packet = &mut Ipv6Packet::new_unchecked(&mut ip_buffer); + let ip_repr = Ipv6Repr { + src_addr: dest_addr, + dst_addr: source_addr, + next_header: IpProtocol::Tcp, + payload_len: tcp_buffer.len(), + hop_limit: 64, + }; + ip_repr.emit(&mut ip_packet); + (ip_packet.header_len(), ip_packet.total_len()) + } + _ => panic!(), + }; + + ip_buffer[header_len..total_len].copy_from_slice(&tcp_buffer); + let packet: &[u8] = &ip_buffer[..total_len]; + packet.to_vec() } fn trace_ip_packet(message: &str, packet: &[u8]) { @@ -294,3 +418,12 @@ fn trace_ip_packet(message: &str, packet: &[u8]) { } } } + +enum RouteResult { + /// The packet can be broadcasted to the virtual interfaces + Broadcast, + /// The packet is not routable so it may be reset. + TcpReset(Vec), + /// The packet can be safely ignored. + Drop, +}