diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index 84cc03a..b628001 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -1,7 +1,7 @@ name: Build Apple Apps on: push: - branches: + branches: - main pull_request: branches: @@ -39,6 +39,7 @@ jobs: - aarch64-apple-darwin env: DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer + PROTOC_VERSION: 3.25.1 steps: - name: Checkout uses: actions/checkout@v3 @@ -54,6 +55,10 @@ jobs: uses: dtolnay/rust-toolchain@stable with: targets: ${{ join(matrix.rust-targets, ', ') }} + - name: Install protoc + uses: taiki-e/install-action@v2 + with: + tool: protoc@${{ env.PROTOC_VERSION }} - name: Build id: build uses: ./.github/actions/build-for-testing @@ -82,4 +87,4 @@ jobs: destination: ${{ matrix.destination }} test-plan: ${{ matrix.xcode-ui-test }} artifact-prefix: ui-tests-${{ matrix.sdk-name }} - check-name: Xcode UI Tests (${{ matrix.platform }}) + check-name: Xcode UI Tests (${{ matrix.platform }}) \ No newline at end of file diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 84ac9d8..95fc628 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -48,6 +48,7 @@ jobs: CARGO_INCREMENTAL: 0 CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc RUST_BACKTRACE: short + PROTOC_VERSION: 3.25.1 steps: - name: Checkout uses: actions/checkout@v3 @@ -64,6 +65,10 @@ jobs: if: matrix.os == 'windows-2022' shell: bash run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH + - name: Install protoc + uses: taiki-e/install-action@v2 + with: + tool: protoc@${{ env.PROTOC_VERSION }} - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -77,4 +82,4 @@ jobs: run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }} - name: Test shell: bash - run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }} + run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 96b2507..997d4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ target/ .DS_STORE .idea/ + +tmp/ + +*.db +*.sock \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a760137..eb85504 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,12 @@ "rust-analyzer.inlayHints.typeHints.enable": false, "rust-analyzer.linkedProjects": [ "./burrow/Cargo.toml" - ] + ], + "[yaml]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced", + "diffEditor.ignoreTrimWhitespace": false, + "editor.formatOnSave": false + } } diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh index e7204a5..00c3652 100755 --- a/Apple/NetworkExtension/libburrow/build-rust.sh +++ b/Apple/NetworkExtension/libburrow/build-rust.sh @@ -68,6 +68,8 @@ else CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin" fi +CARGO_PATH="$(dirname $(readlink -f $(which protoc))):$CARGO_PATH" + # Run cargo without the various environment variables set by Xcode. # Those variables can confuse cargo and the build scripts it runs. env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" diff --git a/Cargo.lock b/Cargo.lock index 5ef886c..309fc08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,17 +132,38 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +dependencies = [ + "async-stream-impl 0.2.1", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ - "async-stream-impl", + "async-stream-impl 0.3.5", "futures-core", "pin-project-lite", ] +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "async-stream-impl" version = "0.3.5" @@ -165,6 +186,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -392,6 +419,8 @@ dependencies = [ "aead", "anyhow", "async-channel", + "async-stream 0.2.1", + "async-stream 0.2.1", "axum 0.7.5", "base64 0.21.7", "blake2", @@ -404,6 +433,7 @@ dependencies = [ "fehler", "futures", "hmac", + "hyper-util", "insta", "ip_network", "ip_network_table", @@ -412,15 +442,24 @@ dependencies = [ "nix 0.27.1", "once_cell", "parking_lot", + "prost 0.13.1", + "prost-types 0.13.1", + "prost 0.13.2", + "prost-types 0.13.2", "rand", "rand_core", "reqwest 0.12.5", "ring", "rusqlite", + "rust-ini", "schemars", "serde", "serde_json", "tokio", + "tokio-stream", + "tonic 0.12.2", + "tonic-build", + "tower", "tracing", "tracing-journald", "tracing-log 0.1.4", @@ -619,9 +658,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" dependencies = [ "futures-core", - "prost", - "prost-types", - "tonic", + "prost 0.12.3", + "prost-types 0.12.3", + "tonic 0.10.2", "tracing-core", ] @@ -637,18 +676,38 @@ dependencies = [ "futures-task", "hdrhistogram", "humantime", - "prost-types", + "prost-types 0.12.3", "serde", "serde_json", "thread_local", "tokio", "tokio-stream", - "tonic", + "tonic 0.10.2", "tracing", "tracing-core", "tracing-subscriber", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -704,6 +763,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -762,6 +827,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -876,6 +950,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.28" @@ -1057,6 +1137,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1215,7 +1314,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.24", "http 0.2.11", "http-body 0.4.6", "httparse", @@ -1238,6 +1337,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -1279,6 +1379,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.4.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1615,6 +1728,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "native-tls" version = "0.2.11" @@ -1762,6 +1881,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -1832,6 +1961,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + [[package]] name = "pin-project" version = "1.1.4" @@ -1925,7 +2064,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.12.3", +] + +[[package]] +name = "prost" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +dependencies = [ + "bytes", + "prost-derive 0.13.2", +] + +[[package]] +name = "prost-build" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.2", + "prost-types 0.13.2", + "regex", + "syn 2.0.48", + "tempfile", ] [[package]] @@ -1941,13 +2111,35 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "prost-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "prost-types" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ - "prost", + "prost 0.12.3", +] + +[[package]] +name = "prost-types" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +dependencies = [ + "prost 0.13.2", ] [[package]] @@ -2100,7 +2292,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.24", "http 0.2.11", "http-body 0.4.6", "hyper 0.14.28", @@ -2197,6 +2389,17 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-ini" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2404,6 +2607,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2654,6 +2866,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2755,25 +2976,59 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ - "async-stream", + "async-stream 0.3.5", "async-trait", "axum 0.6.20", "base64 0.21.7", "bytes", - "h2", + "h2 0.3.24", "http 0.2.11", "http-body 0.4.6", "hyper 0.14.28", - "hyper-timeout", + "hyper-timeout 0.4.1", "percent-encoding", "pin-project", - "prost", + "prost 0.12.3", "tokio", "tokio-stream", "tower", @@ -2782,6 +3037,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +dependencies = [ + "async-stream 0.3.5", + "async-trait", + "axum 0.7.5", + "base64 0.22.1", + "bytes", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-timeout 0.5.1", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.2", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.48", +] + [[package]] name = "tower" version = "0.4.13" @@ -2913,6 +3211,12 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" @@ -3320,6 +3624,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winnow" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Dockerfile b/Dockerfile index 8e17812..404179b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN set -eux && \ curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor --output $KEYRINGS/llvm.gpg && \ echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list && \ apt-get update && \ - apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev && \ + apt-get install --no-install-recommends -y clang-$LLVM_VERSION llvm-$LLVM_VERSION lld-$LLVM_VERSION build-essential sqlite3 libsqlite3-dev musl musl-tools musl-dev protobuf-compiler libprotobuf-dev && \ ln -s clang-$LLVM_VERSION /usr/bin/clang && \ ln -s clang /usr/bin/clang++ && \ ln -s lld-$LLVM_VERSION /usr/bin/ld.lld && \ diff --git a/Makefile b/Makefile index d0c9bd9..6563ab1 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,12 @@ start: stop: @$(cargo_norm) stop +status: + @$(cargo_norm) server-status + +tunnel-config: + @$(cargo_norm) tunnel-config + test-dns: @sudo route delete 8.8.8.8 @sudo route add 8.8.8.8 -interface $(tun) diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 0fb63a5..d5e56c1 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -19,6 +19,7 @@ tokio = { version = "1.37", features = [ "signal", "time", "tracing", + "fs", ] } tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] } clap = { version = "4.4", features = ["derive"] } @@ -56,8 +57,17 @@ reqwest = { version = "0.12", default-features = false, features = [ "json", "rustls-tls", ] } -rusqlite = "0.31.0" +rusqlite = { version = "0.31.0", features = ["blob"] } dotenv = "0.15.0" +tonic = "0.12.0" +prost = "0.13.1" +prost-types = "0.13.1" +tokio-stream = "0.1" +async-stream = "0.2" +tower = "0.4.13" +hyper-util = "0.1.6" +toml = "0.8.15" +rust-ini = "0.21.0" [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" @@ -66,7 +76,7 @@ tracing-journald = "0.3" [target.'cfg(target_vendor = "apple")'.dependencies] nix = { version = "0.27" } -rusqlite = { version = "0.31.0", features = ["bundled"] } +rusqlite = { version = "0.31.0", features = ["bundled", "blob"] } [dev-dependencies] insta = { version = "1.32", features = ["yaml"] } @@ -83,3 +93,7 @@ pre_uninstall_script = "../package/rpm/pre_uninstall" [features] tokio-console = ["dep:console-subscriber"] bundled = ["rusqlite/bundled"] + + +[build-dependencies] +tonic-build = "0.12.0" diff --git a/burrow/build.rs b/burrow/build.rs new file mode 100644 index 0000000..8eea5dc --- /dev/null +++ b/burrow/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("../proto/burrow.proto")?; + Ok(()) +} diff --git a/burrow/burrow.db b/burrow/burrow.db deleted file mode 100644 index c5b6e2c..0000000 Binary files a/burrow/burrow.db and /dev/null differ diff --git a/burrow/src/auth/server/db.rs b/burrow/src/auth/server/db.rs index b74f7ce..995e64b 100644 --- a/burrow/src/auth/server/db.rs +++ b/burrow/src/auth/server/db.rs @@ -1,5 +1,7 @@ use anyhow::Result; +use crate::daemon::rpc::grpc_defs::{Network, NetworkType}; + pub static PATH: &str = "./server.sqlite3"; pub fn init_db() -> Result<()> { diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs index bc506bd..ce96fa5 100644 --- a/burrow/src/daemon/instance.rs +++ b/burrow/src/daemon/instance.rs @@ -1,13 +1,30 @@ use std::{ + ops::Deref, path::{Path, PathBuf}, sync::Arc, + time::Duration, }; use anyhow::Result; -use tokio::{sync::RwLock, task::JoinHandle}; +use rusqlite::Connection; +use tokio::sync::{mpsc, watch, Notify, RwLock}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{Request, Response, Status as RspStatus}; use tracing::{debug, info, warn}; -use tun::tokio::TunInterface; +use tun::{tokio::TunInterface, TunOptions}; +use super::rpc::grpc_defs::{ + networks_server::Networks, + tunnel_server::Tunnel, + Empty, + Network, + NetworkDeleteRequest, + NetworkListResponse, + NetworkReorderRequest, + State as RPCTunnelState, + TunnelConfigurationResponse, + TunnelStatusResponse, +}; use crate::{ daemon::rpc::{ DaemonCommand, @@ -17,114 +34,223 @@ use crate::{ ServerConfig, ServerInfo, }, - database::{get_connection, load_interface}, + database::{ + add_network, + delete_network, + get_connection, + list_networks, + load_interface, + reorder_network, + }, wireguard::{Config, Interface}, }; +#[derive(Debug, Clone)] enum RunState { - Running(JoinHandle>), + Running, Idle, } -pub struct DaemonInstance { - rx: async_channel::Receiver, - sx: async_channel::Sender, - subx: async_channel::Sender, +impl RunState { + pub fn to_rpc(&self) -> RPCTunnelState { + match self { + RunState::Running => RPCTunnelState::Running, + RunState::Idle => RPCTunnelState::Stopped, + } + } +} + +#[derive(Clone)] +pub struct DaemonRPCServer { tun_interface: Arc>>, wg_interface: Arc>, config: Arc>, db_path: Option, - wg_state: RunState, + wg_state_chan: (watch::Sender, watch::Receiver), + network_update_chan: (watch::Sender<()>, watch::Receiver<()>), } -impl DaemonInstance { +impl DaemonRPCServer { pub fn new( - rx: async_channel::Receiver, - sx: async_channel::Sender, - subx: async_channel::Sender, wg_interface: Arc>, config: Arc>, db_path: Option<&Path>, - ) -> Self { - Self { - rx, - sx, - subx, - wg_interface, + ) -> Result { + Ok(Self { tun_interface: Arc::new(RwLock::new(None)), + wg_interface, config, db_path: db_path.map(|p| p.to_owned()), - wg_state: RunState::Idle, - } + wg_state_chan: watch::channel(RunState::Idle), + network_update_chan: watch::channel(()), + }) } - async fn proc_command(&mut self, command: DaemonCommand) -> Result { - info!("Daemon got command: {:?}", command); - match command { - DaemonCommand::Start(st) => { - match self.wg_state { - RunState::Running(_) => { - warn!("Got start, but tun interface already up."); - } - RunState::Idle => { - let tun_if = st.tun.open()?; - debug!("Setting tun on wg_interface"); - self.wg_interface.read().await.set_tun(tun_if).await; - debug!("tun set on wg_interface"); - - debug!("Setting tun_interface"); - self.tun_interface = self.wg_interface.read().await.get_tun(); - debug!("tun_interface set: {:?}", self.tun_interface); - - debug!("Cloning wg_interface"); - let tmp_wg = self.wg_interface.clone(); - let run_task = tokio::spawn(async move { - let twlock = tmp_wg.read().await; - twlock.run().await - }); - self.wg_state = RunState::Running(run_task); - info!("Daemon started tun interface"); - } - } - Ok(DaemonResponseData::None) - } - DaemonCommand::ServerInfo => match &self.tun_interface.read().await.as_ref() { - None => Ok(DaemonResponseData::None), - Some(ti) => { - info!("{:?}", ti); - Ok(DaemonResponseData::ServerInfo(ServerInfo::try_from( - ti.inner.get_ref(), - )?)) - } - }, - DaemonCommand::Stop => { - self.wg_interface.read().await.remove_tun().await; - self.wg_state = RunState::Idle; - Ok(DaemonResponseData::None) - } - DaemonCommand::ServerConfig => { - Ok(DaemonResponseData::ServerConfig(ServerConfig::default())) - } - DaemonCommand::ReloadConfig(interface_id) => { - let conn = get_connection(self.db_path.as_deref())?; - let cfig = load_interface(&conn, &interface_id)?; - *self.config.write().await = cfig; - self.subx - .send(DaemonNotification::ConfigChange(ServerConfig::try_from( - &self.config.read().await.to_owned(), - )?)) - .await?; - Ok(DaemonResponseData::None) - } - } + pub fn get_connection(&self) -> Result { + get_connection(self.db_path.as_deref()).map_err(proc_err) } - pub async fn run(&mut self) -> Result<()> { - while let Ok(command) = self.rx.recv().await { - let response = self.proc_command(command).await; - info!("Daemon response: {:?}", response); - self.sx.send(DaemonResponse::new(response)).await?; - } - Ok(()) + async fn set_wg_state(&self, state: RunState) -> Result<(), RspStatus> { + self.wg_state_chan.0.send(state).map_err(proc_err) + } + + async fn get_wg_state(&self) -> RunState { + self.wg_state_chan.1.borrow().to_owned() + } + + async fn notify_network_update(&self) -> Result<(), RspStatus> { + self.network_update_chan.0.send(()).map_err(proc_err) + } +} + +#[tonic::async_trait] +impl Tunnel for DaemonRPCServer { + type TunnelConfigurationStream = ReceiverStream>; + type TunnelStatusStream = ReceiverStream>; + + async fn tunnel_configuration( + &self, + _request: Request, + ) -> Result, RspStatus> { + let (tx, rx) = mpsc::channel(10); + tokio::spawn(async move { + let serv_config = ServerConfig::default(); + tx.send(Ok(TunnelConfigurationResponse { + mtu: serv_config.mtu.unwrap_or(1000), + addresses: serv_config.address, + })) + .await + }); + Ok(Response::new(ReceiverStream::new(rx))) + } + + async fn tunnel_start(&self, _request: Request) -> Result, RspStatus> { + let wg_state = self.get_wg_state().await; + match wg_state { + RunState::Idle => { + let tun_if = TunOptions::new().open()?; + debug!("Setting tun on wg_interface"); + self.tun_interface.write().await.replace(tun_if); + self.wg_interface + .write() + .await + .set_tun_ref(self.tun_interface.clone()) + .await; + debug!("tun set on wg_interface"); + + debug!("Setting tun_interface"); + debug!("tun_interface set: {:?}", self.tun_interface); + + debug!("Cloning wg_interface"); + let tmp_wg = self.wg_interface.clone(); + let run_task = tokio::spawn(async move { + let twlock = tmp_wg.read().await; + twlock.run().await + }); + self.set_wg_state(RunState::Running).await?; + } + + RunState::Running => { + warn!("Got start, but tun interface already up."); + } + } + + return Ok(Response::new(Empty {})); + } + + async fn tunnel_stop(&self, _request: Request) -> Result, RspStatus> { + self.wg_interface.write().await.remove_tun().await; + self.set_wg_state(RunState::Idle).await?; + return Ok(Response::new(Empty {})); + } + + async fn tunnel_status( + &self, + _request: Request, + ) -> Result, RspStatus> { + let (tx, rx) = mpsc::channel(10); + let mut state_rx = self.wg_state_chan.1.clone(); + tokio::spawn(async move { + let cur = state_rx.borrow_and_update().to_owned(); + tx.send(Ok(status_rsp(cur))).await; + loop { + state_rx.changed().await.unwrap(); + let cur = state_rx.borrow().to_owned(); + let res = tx.send(Ok(status_rsp(cur))).await; + if res.is_err() { + eprintln!("Tunnel status channel closed"); + break; + } + } + }); + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +#[tonic::async_trait] +impl Networks for DaemonRPCServer { + type NetworkListStream = ReceiverStream>; + + async fn network_add(&self, request: Request) -> Result, RspStatus> { + let conn = self.get_connection()?; + let network = request.into_inner(); + add_network(&conn, &network).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } + + async fn network_list( + &self, + _request: Request, + ) -> Result, RspStatus> { + debug!("Mock network_list called"); + let (tx, rx) = mpsc::channel(10); + let conn = self.get_connection()?; + let mut sub = self.network_update_chan.1.clone(); + tokio::spawn(async move { + loop { + let networks = list_networks(&conn) + .map(|res| NetworkListResponse { network: res }) + .map_err(proc_err); + let res = tx.send(networks).await; + if res.is_err() { + eprintln!("Network list channel closed"); + break; + } + sub.changed().await.unwrap(); + } + }); + Ok(Response::new(ReceiverStream::new(rx))) + } + + async fn network_reorder( + &self, + request: Request, + ) -> Result, RspStatus> { + let conn = self.get_connection()?; + reorder_network(&conn, request.into_inner()).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } + + async fn network_delete( + &self, + request: Request, + ) -> Result, RspStatus> { + let conn = self.get_connection()?; + delete_network(&conn, request.into_inner()).map_err(proc_err)?; + self.notify_network_update().await?; + Ok(Response::new(Empty {})) + } +} + +fn proc_err(err: impl ToString) -> RspStatus { + RspStatus::internal(err.to_string()) +} + +fn status_rsp(state: RunState) -> TunnelStatusResponse { + TunnelStatusResponse { + state: state.to_rpc().into(), + start: None, // TODO: Add timestamp } } diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs index 4469e90..f6b973f 100644 --- a/burrow/src/daemon/mod.rs +++ b/burrow/src/daemon/mod.rs @@ -5,14 +5,20 @@ mod instance; mod net; pub mod rpc; -use anyhow::Result; -use instance::DaemonInstance; -pub use net::{DaemonClient, Listener}; +use anyhow::{Error as AhError, Result}; +use instance::DaemonRPCServer; +pub use net::{get_socket_path, DaemonClient}; pub use rpc::{DaemonCommand, DaemonResponseData, DaemonStartOptions}; -use tokio::sync::{Notify, RwLock}; +use tokio::{ + net::UnixListener, + sync::{Notify, RwLock}, +}; +use tokio_stream::wrappers::UnixListenerStream; +use tonic::transport::Server; use tracing::{error, info}; use crate::{ + daemon::rpc::grpc_defs::{networks_server::NetworksServer, tunnel_server::TunnelServer}, database::{get_connection, load_interface}, wireguard::Interface, }; @@ -22,52 +28,36 @@ pub async fn daemon_main( db_path: Option<&Path>, notify_ready: Option>, ) -> Result<()> { - let (commands_tx, commands_rx) = async_channel::unbounded(); - let (response_tx, response_rx) = async_channel::unbounded(); - let (subscribe_tx, subscribe_rx) = async_channel::unbounded(); - - let listener = if let Some(path) = socket_path { - info!("Creating listener... {:?}", path); - Listener::new_with_path(commands_tx, response_rx, subscribe_rx, path) - } else { - info!("Creating listener..."); - Listener::new(commands_tx, response_rx, subscribe_rx) - }; if let Some(n) = notify_ready { n.notify_one() } - let listener = listener?; let conn = get_connection(db_path)?; let config = load_interface(&conn, "1")?; - let iface: Interface = config.clone().try_into()?; - let mut instance = DaemonInstance::new( - commands_rx, - response_tx, - subscribe_tx, - Arc::new(RwLock::new(iface)), + let burrow_server = DaemonRPCServer::new( + Arc::new(RwLock::new(config.clone().try_into()?)), Arc::new(RwLock::new(config)), - db_path, - ); + db_path.clone(), + )?; + let spp = socket_path.clone(); + let tmp = get_socket_path(); + let sock_path = spp.unwrap_or(Path::new(tmp.as_str())); + if sock_path.exists() { + std::fs::remove_file(sock_path)?; + } + let uds = UnixListener::bind(sock_path)?; + let serve_job = tokio::spawn(async move { + let uds_stream = UnixListenerStream::new(uds); + let _srv = Server::builder() + .add_service(TunnelServer::new(burrow_server.clone())) + .add_service(NetworksServer::new(burrow_server)) + .serve_with_incoming(uds_stream) + .await?; + Ok::<(), AhError>(()) + }); info!("Starting daemon..."); - let main_job = tokio::spawn(async move { - let result = instance.run().await; - if let Err(e) = result.as_ref() { - error!("Instance exited: {}", e); - } - result - }); - - let listener_job = tokio::spawn(async move { - let result = listener.run().await; - if let Err(e) = result.as_ref() { - error!("Listener exited: {}", e); - } - result - }); - - tokio::try_join!(main_job, listener_job) + tokio::try_join!(serve_job) .map(|_| ()) .map_err(|e| e.into()) } diff --git a/burrow/src/daemon/net/mod.rs b/burrow/src/daemon/net/mod.rs index 242f479..eb45335 100644 --- a/burrow/src/daemon/net/mod.rs +++ b/burrow/src/daemon/net/mod.rs @@ -1,18 +1,11 @@ - - - - - #[cfg(target_family = "unix")] mod unix; #[cfg(target_family = "unix")] -pub use unix::{DaemonClient, Listener}; +pub use unix::{get_socket_path, DaemonClient, Listener}; #[cfg(target_os = "windows")] mod windows; #[cfg(target_os = "windows")] pub use windows::{DaemonClient, Listener}; - - diff --git a/burrow/src/daemon/net/unix.rs b/burrow/src/daemon/net/unix.rs index 70c4207..975c470 100644 --- a/burrow/src/daemon/net/unix.rs +++ b/burrow/src/daemon/net/unix.rs @@ -25,7 +25,7 @@ const UNIX_SOCKET_PATH: &str = "/run/burrow.sock"; #[cfg(target_vendor = "apple")] const UNIX_SOCKET_PATH: &str = "burrow.sock"; -fn get_socket_path() -> String { +pub fn get_socket_path() -> String { if std::env::var("BURROW_SOCKET_PATH").is_ok() { return std::env::var("BURROW_SOCKET_PATH").unwrap(); } @@ -36,7 +36,7 @@ pub struct Listener { cmd_tx: async_channel::Sender, rsp_rx: async_channel::Receiver, sub_chan: async_channel::Receiver, - inner: UnixListener, + pub inner: UnixListener, } impl Listener { diff --git a/burrow/src/daemon/rpc/client.rs b/burrow/src/daemon/rpc/client.rs new file mode 100644 index 0000000..862e34c --- /dev/null +++ b/burrow/src/daemon/rpc/client.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use hyper_util::rt::TokioIo; +use tokio::net::UnixStream; +use tonic::transport::{Endpoint, Uri}; +use tower::service_fn; + +use super::grpc_defs::{networks_client::NetworksClient, tunnel_client::TunnelClient}; +use crate::daemon::get_socket_path; + +pub struct BurrowClient { + pub networks_client: NetworksClient, + pub tunnel_client: TunnelClient, +} + +impl BurrowClient { + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + pub async fn from_uds() -> Result { + let channel = Endpoint::try_from("http://[::]:50051")? // NOTE: this is a hack(?) + .connect_with_connector(service_fn(|_: Uri| async { + let sock_path = get_socket_path(); + Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(sock_path).await?)) + })) + .await?; + let nw_client = NetworksClient::new(channel.clone()); + let tun_client = TunnelClient::new(channel.clone()); + Ok(BurrowClient { + networks_client: nw_client, + tunnel_client: tun_client, + }) + } +} diff --git a/burrow/src/daemon/rpc/grpc_defs.rs b/burrow/src/daemon/rpc/grpc_defs.rs new file mode 100644 index 0000000..f3085ee --- /dev/null +++ b/burrow/src/daemon/rpc/grpc_defs.rs @@ -0,0 +1,5 @@ +pub use burrowgrpc::*; + +mod burrowgrpc { + tonic::include_proto!("burrow"); +} diff --git a/burrow/src/daemon/rpc/mod.rs b/burrow/src/daemon/rpc/mod.rs index 4146e71..512662c 100644 --- a/burrow/src/daemon/rpc/mod.rs +++ b/burrow/src/daemon/rpc/mod.rs @@ -1,7 +1,10 @@ +pub mod client; +pub mod grpc_defs; pub mod notification; pub mod request; pub mod response; +pub use client::BurrowClient; pub use notification::DaemonNotification; pub use request::{DaemonCommand, DaemonRequest, DaemonStartOptions}; pub use response::{DaemonResponse, DaemonResponseData, ServerConfig, ServerInfo}; diff --git a/burrow/src/database.rs b/burrow/src/database.rs index 0047b01..9a9aac3 100644 --- a/burrow/src/database.rs +++ b/burrow/src/database.rs @@ -3,7 +3,15 @@ use std::path::Path; use anyhow::Result; use rusqlite::{params, Connection}; -use crate::wireguard::config::{Config, Interface, Peer}; +use crate::{ + daemon::rpc::grpc_defs::{ + Network as RPCNetwork, + NetworkDeleteRequest, + NetworkReorderRequest, + NetworkType, + }, + wireguard::config::{Config, Interface, Peer}, +}; #[cfg(target_vendor = "apple")] const DB_PATH: &str = "burrow.db"; @@ -30,8 +38,20 @@ const CREATE_WG_PEER_TABLE: &str = "CREATE TABLE IF NOT EXISTS wg_peer ( )"; const CREATE_NETWORK_TABLE: &str = "CREATE TABLE IF NOT EXISTS network ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + payload BLOB, + idx INTEGER, interface_id INT REFERENCES wg_interface(id) ON UPDATE CASCADE -)"; +); +CREATE TRIGGER IF NOT EXISTS increment_network_idx +AFTER INSERT ON network +BEGIN + UPDATE network + SET idx = (SELECT COALESCE(MAX(idx), 0) + 1 FROM network) + WHERE id = NEW.id; +END; +"; pub fn initialize_tables(conn: &Connection) -> Result<()> { conn.execute(CREATE_WG_INTERFACE_TABLE, [])?; @@ -40,20 +60,6 @@ pub fn initialize_tables(conn: &Connection) -> Result<()> { Ok(()) } -fn parse_lst(s: &str) -> Vec { - if s.is_empty() { - return vec![]; - } - s.split(',').map(|s| s.to_string()).collect() -} - -fn to_lst(v: &Vec) -> String { - v.iter() - .map(|s| s.to_string()) - .collect::>() - .join(",") -} - pub fn load_interface(conn: &Connection, interface_id: &str) -> Result { let iface = conn.query_row( "SELECT private_key, dns, address, listen_port, mtu FROM wg_interface WHERE id = ?", @@ -99,7 +105,7 @@ pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> { cif.private_key, to_lst(&cif.dns), to_lst(&cif.address), - cif.listen_port, + cif.listen_port.unwrap_or(51820), cif.mtu ])?; let interface_id = conn.last_insert_rowid(); @@ -127,10 +133,75 @@ pub fn get_connection(path: Option<&Path>) -> Result { Ok(Connection::open(p)?) } +pub fn add_network(conn: &Connection, network: &RPCNetwork) -> Result<()> { + let mut stmt = conn.prepare("INSERT INTO network (id, type, payload) VALUES (?, ?, ?)")?; + stmt.execute(params![ + network.id, + network.r#type().as_str_name(), + &network.payload + ])?; + if network.r#type() == NetworkType::WireGuard { + let payload_str = String::from_utf8(network.payload.clone())?; + let wg_config = Config::from_content_fmt(&payload_str, "ini")?; + dump_interface(conn, &wg_config)?; + } + Ok(()) +} + +pub fn list_networks(conn: &Connection) -> Result> { + let mut stmt = conn.prepare("SELECT id, type, payload FROM network ORDER BY idx")?; + let networks: Vec = stmt + .query_map([], |row| { + println!("row: {:?}", row); + let network_id: i32 = row.get(0)?; + let network_type: String = row.get(1)?; + let network_type = NetworkType::from_str_name(network_type.as_str()) + .ok_or(rusqlite::Error::InvalidQuery)?; + let payload: Vec = row.get(2)?; + Ok(RPCNetwork { + id: network_id, + r#type: network_type.into(), + payload: payload.into(), + }) + })? + .collect::, rusqlite::Error>>()?; + Ok(networks) +} + +pub fn reorder_network(conn: &Connection, req: NetworkReorderRequest) -> Result<()> { + let mut stmt = conn.prepare("UPDATE network SET idx = ? WHERE id = ?")?; + let res = stmt.execute(params![req.index, req.id])?; + if res == 0 { + return Err(anyhow::anyhow!("No such network exists")); + } + Ok(()) +} + +pub fn delete_network(conn: &Connection, req: NetworkDeleteRequest) -> Result<()> { + let mut stmt = conn.prepare("DELETE FROM network WHERE id = ?")?; + let res = stmt.execute(params![req.id])?; + if res == 0 { + return Err(anyhow::anyhow!("No such network exists")); + } + Ok(()) +} + +fn parse_lst(s: &str) -> Vec { + if s.is_empty() { + return vec![]; + } + s.split(',').map(|s| s.to_string()).collect() +} + +fn to_lst(v: &Vec) -> String { + v.iter() + .map(|s| s.to_string()) + .collect::>() + .join(",") +} + #[cfg(test)] mod tests { - use std::path::Path; - use super::*; #[test] diff --git a/burrow/src/main.rs b/burrow/src/main.rs index ff07d4c..e87b4c9 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -11,8 +11,7 @@ mod wireguard; mod auth; #[cfg(any(target_os = "linux", target_vendor = "apple"))] -use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions}; -use tun::TunOptions; +use daemon::{DaemonClient, DaemonCommand}; #[cfg(any(target_os = "linux", target_vendor = "apple"))] use crate::daemon::DaemonResponseData; @@ -20,6 +19,9 @@ use crate::daemon::DaemonResponseData; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub mod database; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +use crate::daemon::rpc::{grpc_defs::Empty, BurrowClient}; + #[derive(Parser)] #[command(name = "Burrow")] #[command(author = "Hack Club ")] @@ -52,13 +54,24 @@ enum Commands { ReloadConfig(ReloadConfigArgs), /// Authentication server AuthServer, + /// Server Status + ServerStatus, + /// Tunnel Config + TunnelConfig, + /// Add Network + NetworkAdd(NetworkAddArgs), + /// List Networks + NetworkList, + /// Reorder Network + NetworkReorder(NetworkReorderArgs), + /// Delete Network + NetworkDelete(NetworkDeleteArgs), } #[derive(Args)] struct ReloadConfigArgs { #[clap(long, short)] interface_id: String, - } #[derive(Args)] @@ -67,21 +80,132 @@ struct StartArgs {} #[derive(Args)] struct DaemonArgs {} +#[derive(Args)] +struct NetworkAddArgs { + id: i32, + network_type: i32, + payload_path: String, +} + +#[derive(Args)] +struct NetworkReorderArgs { + id: i32, + index: i32, +} + +#[derive(Args)] +struct NetworkDeleteArgs { + id: i32, +} + #[cfg(any(target_os = "linux", target_vendor = "apple"))] async fn try_start() -> Result<()> { - let mut client = DaemonClient::new().await?; - client - .send_command(DaemonCommand::Start(DaemonStartOptions { - tun: TunOptions::new().address(vec!["10.13.13.2", "::2"]), - })) - .await - .map(|_| ()) + let mut client = BurrowClient::from_uds().await?; + let res = client.tunnel_client.tunnel_start(Empty {}).await?; + println!("Got results! {:?}", res); + Ok(()) } #[cfg(any(target_os = "linux", target_vendor = "apple"))] async fn try_stop() -> Result<()> { - let mut client = DaemonClient::new().await?; - client.send_command(DaemonCommand::Stop).await?; + let mut client = BurrowClient::from_uds().await?; + let res = client.tunnel_client.tunnel_stop(Empty {}).await?; + println!("Got results! {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_serverstatus() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .tunnel_client + .tunnel_status(Empty {}) + .await? + .into_inner(); + if let Some(st) = res.message().await? { + println!("Server Status: {:?}", st); + } else { + println!("Server Status is None"); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_tun_config() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .tunnel_client + .tunnel_configuration(Empty {}) + .await? + .into_inner(); + if let Some(config) = res.message().await? { + println!("Tunnel Config: {:?}", config); + } else { + println!("Tunnel Config is None"); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_add(id: i32, network_type: i32, payload_path: &str) -> Result<()> { + use tokio::{fs::File, io::AsyncReadExt}; + + use crate::daemon::rpc::grpc_defs::Network; + + let mut file = File::open(payload_path).await?; + let mut payload = Vec::new(); + file.read_to_end(&mut payload).await?; + + let mut client = BurrowClient::from_uds().await?; + let network = Network { + id, + r#type: network_type, + payload, + }; + let res = client.networks_client.network_add(network).await?; + println!("Network Add Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_list() -> Result<()> { + let mut client = BurrowClient::from_uds().await?; + let mut res = client + .networks_client + .network_list(Empty {}) + .await? + .into_inner(); + while let Some(network_list) = res.message().await? { + println!("Network List: {:?}", network_list); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_reorder(id: i32, index: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::NetworkReorderRequest; + + let mut client = BurrowClient::from_uds().await?; + let reorder_request = NetworkReorderRequest { id, index }; + let res = client + .networks_client + .network_reorder(reorder_request) + .await?; + println!("Network Reorder Response: {:?}", res); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_network_delete(id: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::NetworkDeleteRequest; + + let mut client = BurrowClient::from_uds().await?; + let delete_request = NetworkDeleteRequest { id }; + let res = client + .networks_client + .network_delete(delete_request) + .await?; + println!("Network Delete Response: {:?}", res); Ok(()) } @@ -153,6 +277,14 @@ async fn main() -> Result<()> { Commands::ServerConfig => try_serverconfig().await?, Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, Commands::AuthServer => crate::auth::server::serve().await?, + Commands::ServerStatus => try_serverstatus().await?, + Commands::TunnelConfig => try_tun_config().await?, + Commands::NetworkAdd(args) => { + try_network_add(args.id, args.network_type, &args.payload_path).await? + } + Commands::NetworkList => try_network_list().await?, + Commands::NetworkReorder(args) => try_network_reorder(args.id, args.index).await?, + Commands::NetworkDelete(args) => try_network_delete(args.id).await?, } Ok(()) diff --git a/burrow/src/wireguard/config.rs b/burrow/src/wireguard/config.rs index bd86a9f..5766675 100644 --- a/burrow/src/wireguard/config.rs +++ b/burrow/src/wireguard/config.rs @@ -3,9 +3,12 @@ use std::{net::ToSocketAddrs, str::FromStr}; use anyhow::{anyhow, Error, Result}; use base64::{engine::general_purpose, Engine}; use fehler::throws; +use ini::{Ini, Properties}; use ip_network::IpNetwork; +use serde::{Deserialize, Serialize}; use x25519_dalek::{PublicKey, StaticSecret}; +use super::inifield::IniField; use crate::wireguard::{Interface as WgInterface, Peer as WgPeer}; #[throws] @@ -31,7 +34,7 @@ fn parse_public_key(string: &str) -> PublicKey { /// A raw version of Peer Config that can be used later to reflect configuration files. /// This should be later converted to a `WgPeer`. /// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Peer { pub public_key: String, pub preshared_key: Option, @@ -41,17 +44,18 @@ pub struct Peer { pub name: Option, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Interface { pub private_key: String, pub address: Vec, - pub listen_port: u32, + pub listen_port: Option, pub dns: Vec, pub mtu: Option, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Config { + #[serde(rename = "Peer")] pub peers: Vec, pub interface: Interface, // Support for multiple interfaces? } @@ -98,7 +102,7 @@ impl Default for Config { interface: Interface { private_key: "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=".into(), address: vec!["10.13.13.2/24".into()], - listen_port: 51820, + listen_port: Some(51820), dns: Default::default(), mtu: Default::default(), }, @@ -113,3 +117,83 @@ impl Default for Config { } } } + +fn props_get(props: &Properties, key: &str) -> Result +where + T: TryFrom, +{ + IniField::try_from(props.get(key))?.try_into() +} + +impl TryFrom<&Properties> for Interface { + type Error = anyhow::Error; + + fn try_from(props: &Properties) -> Result { + Ok(Self { + private_key: props_get(props, "PrivateKey")?, + address: props_get(props, "Address")?, + listen_port: props_get(props, "ListenPort")?, + dns: props_get(props, "DNS")?, + mtu: props_get(props, "MTU")?, + }) + } +} + +impl TryFrom<&Properties> for Peer { + type Error = anyhow::Error; + + fn try_from(props: &Properties) -> Result { + Ok(Self { + public_key: props_get(props, "PublicKey")?, + preshared_key: props_get(props, "PresharedKey")?, + allowed_ips: props_get(props, "AllowedIPs")?, + endpoint: props_get(props, "Endpoint")?, + persistent_keepalive: props_get(props, "PersistentKeepalive")?, + name: props_get(props, "Name")?, + }) + } +} + +impl Config { + pub fn from_toml(toml: &str) -> Result { + toml::from_str(toml).map_err(Into::into) + } + + pub fn from_ini(ini: &str) -> Result { + let ini = Ini::load_from_str(ini)?; + let interface = ini + .section(Some("Interface")) + .ok_or(anyhow!("Interface section not found"))?; + let peers = ini.section_all(Some("Peer")); + Ok(Self { + interface: Interface::try_from(interface)?, + peers: peers + .into_iter() + .map(|v| Peer::try_from(v)) + .collect::>>()?, + }) + } + + pub fn from_content_fmt(content: &str, fmt: &str) -> Result { + match fmt { + "toml" => Self::from_toml(content), + "ini" | "conf" => Self::from_ini(content), + _ => Err(anyhow::anyhow!("Unsupported format: {}", fmt)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tst_config_toml() { + let cfig = Config::default(); + let toml = toml::to_string(&cfig).unwrap(); + println!("{}", &toml); + insta::assert_snapshot!(toml); + let cfig2: Config = toml::from_str(&toml).unwrap(); + assert_eq!(cfig, cfig2); + } +} diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs index 84b5489..321801b 100755 --- a/burrow/src/wireguard/iface.rs +++ b/burrow/src/wireguard/iface.rs @@ -93,6 +93,12 @@ impl Interface { *st = IfaceStatus::Running; } + pub async fn set_tun_ref(&mut self, tun: Arc>>) { + self.tun = tun; + let mut st = self.status.write().await; + *st = IfaceStatus::Running; + } + pub fn get_tun(&self) -> Arc>> { self.tun.clone() } @@ -135,7 +141,7 @@ impl Interface { Some(addr) => addr, None => { debug!("No destination found"); - continue + continue; } }; @@ -154,7 +160,7 @@ impl Interface { } Err(e) => { log::error!("Failed to send packet {}", e); - continue + continue; } }; } @@ -175,7 +181,7 @@ impl Interface { let main_tsk = async move { if let Err(e) = pcb.open_if_closed().await { log::error!("failed to open pcb: {}", e); - return + return; } let r2 = pcb.run(tun).await; if let Err(e) = r2 { @@ -195,7 +201,7 @@ impl Interface { Ok(..) => (), Err(e) => { error!("Failed to update timers: {}", e); - return + return; } } } diff --git a/burrow/src/wireguard/inifield.rs b/burrow/src/wireguard/inifield.rs new file mode 100644 index 0000000..946868d --- /dev/null +++ b/burrow/src/wireguard/inifield.rs @@ -0,0 +1,81 @@ +use std::str::FromStr; + +use anyhow::{Error, Result}; + +pub struct IniField(String); + +impl FromStr for IniField { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string())) + } +} + +impl TryFrom for Vec { + type Error = Error; + + fn try_from(field: IniField) -> Result { + Ok(field.0.split(',').map(|s| s.trim().to_string()).collect()) + } +} + +impl TryFrom for u32 { + type Error = Error; + + fn try_from(value: IniField) -> Result { + value.0.parse().map_err(Error::from) + } +} + +impl TryFrom for Option { + type Error = Error; + + fn try_from(value: IniField) -> Result { + if value.0.is_empty() { + Ok(None) + } else { + value.0.parse().map(Some).map_err(Error::from) + } + } +} + +impl TryFrom for String { + type Error = Error; + + fn try_from(value: IniField) -> Result { + Ok(value.0) + } +} + +impl TryFrom for Option { + type Error = Error; + + fn try_from(value: IniField) -> Result { + if value.0.is_empty() { + Ok(None) + } else { + Ok(Some(value.0)) + } + } +} + +impl TryFrom> for IniField +where + T: ToString, +{ + type Error = Error; + + fn try_from(value: Option) -> Result { + Ok(match value { + Some(v) => Self(v.to_string()), + None => Self(String::new()), + }) + } +} + +impl IniField { + fn new(value: &str) -> Self { + Self(value.to_string()) + } +} diff --git a/burrow/src/wireguard/mod.rs b/burrow/src/wireguard/mod.rs index 4c70a7f..cfb4585 100755 --- a/burrow/src/wireguard/mod.rs +++ b/burrow/src/wireguard/mod.rs @@ -1,5 +1,6 @@ pub mod config; mod iface; +mod inifield; mod noise; mod pcb; mod peer; diff --git a/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap b/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap new file mode 100644 index 0000000..3800647 --- /dev/null +++ b/burrow/src/wireguard/snapshots/burrow__wireguard__config__tests__tst_config_toml.snap @@ -0,0 +1,16 @@ +--- +source: burrow/src/wireguard/config.rs +expression: toml +--- +[[Peer]] +public_key = "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM=" +preshared_key = "ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698=" +allowed_ips = ["8.8.8.8/32", "0.0.0.0/0"] +endpoint = "wg.burrow.rs:51820" + +[interface] +private_key = "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=" +address = ["10.13.13.2/24"] +listen_port = 51820 +dns = [] + diff --git a/burrow/tmp/conrd.conf b/burrow/tmp/conrd.conf new file mode 100644 index 0000000..52572d1 --- /dev/null +++ b/burrow/tmp/conrd.conf @@ -0,0 +1,8 @@ +[Interface] +PrivateKey = gAaK0KFGOpxY7geGo59XXDufcxeoSNXXNC12mCQmlVs= +Address = 10.1.11.2/32 +DNS = 10.1.11.1 +[Peer] +PublicKey = Ab6V2mgPHiCXaAZfQrNts8ha8RkEzC49VnmMQfe5Yg4= +AllowedIPs = 10.1.11.1/32,10.1.11.2/32,0.0.0.0/0 +Endpoint = 172.251.163.175:51820 \ No newline at end of file diff --git a/proto/burrow.proto b/proto/burrow.proto index 2d29c78..2355b8d 100644 --- a/proto/burrow.proto +++ b/proto/burrow.proto @@ -11,7 +11,7 @@ service Tunnel { } service Networks { - rpc NetworkAdd (Empty) returns (Empty); + rpc NetworkAdd (Network) returns (Empty); rpc NetworkList (Empty) returns (stream NetworkListResponse); rpc NetworkReorder (NetworkReorderRequest) returns (Empty); rpc NetworkDelete (NetworkDeleteRequest) returns (Empty);