GRPC Server Support

- Deprecates old json-rpc system
- Add GRPC daemon over uds
This commit is contained in:
Jett Chen 2024-07-13 17:32:49 -07:00
parent 3fbb520a10
commit e4b0f1660b
28 changed files with 1110 additions and 200 deletions

View file

@ -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 }})

View file

@ -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 ') }}

5
.gitignore vendored
View file

@ -7,3 +7,8 @@ target/
.DS_STORE
.idea/
tmp/
*.db
*.sock

View file

@ -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
}
}

View file

@ -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[@]}"

341
Cargo.lock generated
View file

@ -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"

View file

@ -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 && \

View file

@ -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)

View file

@ -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"

4
burrow/build.rs Normal file
View file

@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("../proto/burrow.proto")?;
Ok(())
}

Binary file not shown.

View file

@ -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<()> {

View file

@ -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<Result<()>>),
Running,
Idle,
}
pub struct DaemonInstance {
rx: async_channel::Receiver<DaemonCommand>,
sx: async_channel::Sender<DaemonResponse>,
subx: async_channel::Sender<DaemonNotification>,
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<RwLock<Option<TunInterface>>>,
wg_interface: Arc<RwLock<Interface>>,
config: Arc<RwLock<Config>>,
db_path: Option<PathBuf>,
wg_state: RunState,
wg_state_chan: (watch::Sender<RunState>, watch::Receiver<RunState>),
network_update_chan: (watch::Sender<()>, watch::Receiver<()>),
}
impl DaemonInstance {
impl DaemonRPCServer {
pub fn new(
rx: async_channel::Receiver<DaemonCommand>,
sx: async_channel::Sender<DaemonResponse>,
subx: async_channel::Sender<DaemonNotification>,
wg_interface: Arc<RwLock<Interface>>,
config: Arc<RwLock<Config>>,
db_path: Option<&Path>,
) -> Self {
Self {
rx,
sx,
subx,
wg_interface,
) -> Result<Self> {
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<DaemonResponseData> {
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<Connection, RspStatus> {
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<Result<TunnelConfigurationResponse, RspStatus>>;
type TunnelStatusStream = ReceiverStream<Result<TunnelStatusResponse, RspStatus>>;
async fn tunnel_configuration(
&self,
_request: Request<Empty>,
) -> Result<Response<Self::TunnelConfigurationStream>, 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<Empty>) -> Result<Response<Empty>, 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<Empty>) -> Result<Response<Empty>, 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<Empty>,
) -> Result<Response<Self::TunnelStatusStream>, 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<Result<NetworkListResponse, RspStatus>>;
async fn network_add(&self, request: Request<Network>) -> Result<Response<Empty>, 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<Empty>,
) -> Result<Response<Self::NetworkListStream>, 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<NetworkReorderRequest>,
) -> Result<Response<Empty>, 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<NetworkDeleteRequest>,
) -> Result<Response<Empty>, 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
}
}

View file

@ -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<Arc<Notify>>,
) -> 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())
}

View file

@ -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};

View file

@ -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<DaemonCommand>,
rsp_rx: async_channel::Receiver<DaemonResponse>,
sub_chan: async_channel::Receiver<DaemonNotification>,
inner: UnixListener,
pub inner: UnixListener,
}
impl Listener {

View file

@ -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<T> {
pub networks_client: NetworksClient<T>,
pub tunnel_client: TunnelClient<T>,
}
impl BurrowClient<tonic::transport::Channel> {
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub async fn from_uds() -> Result<Self> {
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,
})
}
}

View file

@ -0,0 +1,5 @@
pub use burrowgrpc::*;
mod burrowgrpc {
tonic::include_proto!("burrow");
}

View file

@ -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};

View file

@ -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<String> {
if s.is_empty() {
return vec![];
}
s.split(',').map(|s| s.to_string()).collect()
}
fn to_lst<T: ToString>(v: &Vec<T>) -> String {
v.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join(",")
}
pub fn load_interface(conn: &Connection, interface_id: &str) -> Result<Config> {
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<Connection> {
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<Vec<RPCNetwork>> {
let mut stmt = conn.prepare("SELECT id, type, payload FROM network ORDER BY idx")?;
let networks: Vec<RPCNetwork> = 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<u8> = row.get(2)?;
Ok(RPCNetwork {
id: network_id,
r#type: network_type.into(),
payload: payload.into(),
})
})?
.collect::<Result<Vec<RPCNetwork>, 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<String> {
if s.is_empty() {
return vec![];
}
s.split(',').map(|s| s.to_string()).collect()
}
fn to_lst<T: ToString>(v: &Vec<T>) -> String {
v.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join(",")
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
#[test]

View file

@ -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 <team@hackclub.com>")]
@ -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(())

View file

@ -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<String>,
@ -41,17 +44,18 @@ pub struct Peer {
pub name: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Interface {
pub private_key: String,
pub address: Vec<String>,
pub listen_port: u32,
pub listen_port: Option<u32>,
pub dns: Vec<String>,
pub mtu: Option<u32>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Config {
#[serde(rename = "Peer")]
pub peers: Vec<Peer>,
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<T>(props: &Properties, key: &str) -> Result<T>
where
T: TryFrom<IniField, Error = anyhow::Error>,
{
IniField::try_from(props.get(key))?.try_into()
}
impl TryFrom<&Properties> for Interface {
type Error = anyhow::Error;
fn try_from(props: &Properties) -> Result<Self, Error> {
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<Self, Error> {
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<Self> {
toml::from_str(toml).map_err(Into::into)
}
pub fn from_ini(ini: &str) -> Result<Self> {
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::<Result<Vec<Peer>>>()?,
})
}
pub fn from_content_fmt(content: &str, fmt: &str) -> Result<Self> {
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);
}
}

View file

@ -93,6 +93,12 @@ impl Interface {
*st = IfaceStatus::Running;
}
pub async fn set_tun_ref(&mut self, tun: Arc<RwLock<Option<TunInterface>>>) {
self.tun = tun;
let mut st = self.status.write().await;
*st = IfaceStatus::Running;
}
pub fn get_tun(&self) -> Arc<RwLock<Option<TunInterface>>> {
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;
}
}
}

View file

@ -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<Self, Self::Err> {
Ok(Self(s.to_string()))
}
}
impl TryFrom<IniField> for Vec<String> {
type Error = Error;
fn try_from(field: IniField) -> Result<Self, Self::Error> {
Ok(field.0.split(',').map(|s| s.trim().to_string()).collect())
}
}
impl TryFrom<IniField> for u32 {
type Error = Error;
fn try_from(value: IniField) -> Result<Self, Self::Error> {
value.0.parse().map_err(Error::from)
}
}
impl TryFrom<IniField> for Option<u32> {
type Error = Error;
fn try_from(value: IniField) -> Result<Self, Self::Error> {
if value.0.is_empty() {
Ok(None)
} else {
value.0.parse().map(Some).map_err(Error::from)
}
}
}
impl TryFrom<IniField> for String {
type Error = Error;
fn try_from(value: IniField) -> Result<Self, Self::Error> {
Ok(value.0)
}
}
impl TryFrom<IniField> for Option<String> {
type Error = Error;
fn try_from(value: IniField) -> Result<Self, Self::Error> {
if value.0.is_empty() {
Ok(None)
} else {
Ok(Some(value.0))
}
}
}
impl<T> TryFrom<Option<T>> for IniField
where
T: ToString,
{
type Error = Error;
fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
Ok(match value {
Some(v) => Self(v.to_string()),
None => Self(String::new()),
})
}
}
impl IniField {
fn new(value: &str) -> Self {
Self(value.to_string())
}
}

View file

@ -1,5 +1,6 @@
pub mod config;
mod iface;
mod inifield;
mod noise;
mod pcb;
mod peer;

View file

@ -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 = []

8
burrow/tmp/conrd.conf Normal file
View file

@ -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

View file

@ -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);