diff --git a/.cargo/config.toml b/.cargo/config.toml index 302ce48..767d03a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,3 @@ -[target.'cfg(unix)'] -runner = "sudo -E" - [alias] # command aliases rr = "run --release" bb = "build --release" diff --git a/.forgejo/workflows/build-rust.yml b/.forgejo/workflows/build-rust.yml new file mode 100644 index 0000000..2df1ad3 --- /dev/null +++ b/.forgejo/workflows/build-rust.yml @@ -0,0 +1,31 @@ +name: Build Rust + +on: + push: + branches: + - main + pull_request: + branches: + - "**" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust: + name: Cargo Test + runs-on: [self-hosted, linux, x86_64, burrow-forge] + steps: + - name: Checkout + uses: https://code.forgejo.org/actions/checkout@v4 + with: + token: ${{ github.token }} + fetch-depth: 0 + + - name: Test + shell: bash + run: | + set -euo pipefail + nix develop .#ci -c cargo test --workspace --all-features diff --git a/.forgejo/workflows/build-site.yml b/.forgejo/workflows/build-site.yml new file mode 100644 index 0000000..6f7c5e2 --- /dev/null +++ b/.forgejo/workflows/build-site.yml @@ -0,0 +1,31 @@ +name: Build Site + +on: + push: + branches: + - main + pull_request: + branches: + - "**" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + site: + name: Next.js Build + runs-on: [self-hosted, linux, x86_64, burrow-forge] + steps: + - name: Checkout + uses: https://code.forgejo.org/actions/checkout@v4 + with: + token: ${{ github.token }} + fetch-depth: 0 + + - name: Build + shell: bash + run: | + set -euo pipefail + nix develop .#ci -c bash -lc 'cd site && npm install && npm run build' diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index 7ae8c4c..5a135b4 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -54,6 +54,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: + toolchain: 1.85.0 targets: ${{ join(matrix.rust-targets, ', ') }} - name: Install Protobuf shell: bash @@ -86,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 }}) \ No newline at end of file + check-name: Xcode UI Tests (${{ matrix.platform }}) diff --git a/.github/workflows/build-rust.yml b/.github/workflows/build-rust.yml index 95fc628..cbbdd81 100644 --- a/.github/workflows/build-rust.yml +++ b/.github/workflows/build-rust.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - "*" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build: name: Build Crate (${{ matrix.platform }}) @@ -72,14 +75,14 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: stable + toolchain: 1.85.0 components: rustfmt targets: ${{ join(matrix.targets, ', ') }} - name: Setup Rust Cache uses: Swatinem/rust-cache@v2 - name: Build shell: bash - run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }} + run: cargo build --locked --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 ') }} \ No newline at end of file + run: cargo test --locked --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }} diff --git a/.github/workflows/release-apple.yml b/.github/workflows/release-apple.yml index c869d6a..b36ed73 100644 --- a/.github/workflows/release-apple.yml +++ b/.github/workflows/release-apple.yml @@ -47,6 +47,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: + toolchain: 1.85.0 targets: ${{ join(matrix.rust-targets, ', ') }} - name: Install Protobuf shell: bash diff --git a/.gitignore b/.gitignore index 1b300b4..7efe903 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Xcode xcuserdata +Apple/build/ # Swift Apple/Package/.swiftpm/ @@ -12,6 +13,8 @@ target/ .idea/ tmp/ +intake/ *.db -*.sock \ No newline at end of file +*.sqlite3 +*.sock diff --git a/CONSTITUTION.md b/CONSTITUTION.md new file mode 100644 index 0000000..f97e683 --- /dev/null +++ b/CONSTITUTION.md @@ -0,0 +1,38 @@ +# Burrow Constitution + +1. Mission + +Burrow exists to build a proper VPN: fast, inspectable, deployable on infrastructure the project controls, and legible enough that future contributors can extend it without guesswork. + +2. Commitments + +- Protocol work must favor correctness over novelty. Burrow does not claim support for a transport or control-plane feature until the wire format, state handling, and recovery behavior are implemented and tested. +- Security is a design constraint, not a cleanup phase. Key material, bootstrap credentials, control-plane tokens, and routing policy must have explicit storage and rotation paths. +- Performance matters. Burrow should avoid needless copies, hidden blocking, and ad hoc process graphs that make packet forwarding or control-plane convergence harder to reason about. +- Source, infrastructure, and release logic live in the repository. If the forge cannot be rebuilt from the tree, the work is incomplete. +- Non-trivial changes require a Burrow Evolution Proposal. Durable rationale belongs in the repository, not only in chat. + +3. Infrastructure + +Burrow controls its own forge, runners, deployment automation, and edge configuration for `burrow.net` and `burrow.rs`. + +- Dedicated compute is preferred over SaaS dependencies when the dependency would hold release, source, or identity authority. +- Secrets may be bootstrapped from local intake for initial bring-up, but long-lived operation must converge on encrypted, versioned secret handling. +- Production access must be attributable. Automation identities, SSH keys, and service accounts must be named and documented. + +4. Contributors + +- Read this constitution before drafting product, protocol, or infrastructure changes. +- Capture intent, testing expectations, and rollback procedures in proposals. +- Prefer reversible migrations. If a change is destructive, document the preconditions and teardown plan first. +- Security-sensitive work requires explicit reviewer attention, even when the implementation is performed by an agent. + +5. Governance + +- Burrow Evolution Proposals (BEPs) are the primary design record for architectural, protocol, forge, and deployment changes. +- Accepted proposals are authoritative until superseded. +- Constitutional changes require a dedicated proposal that quotes the affected text and records the decision. + +6. Origin + +Burrow started as a firewall-burrowing client and now carries its own transport, daemon, mesh, and control-plane work. This constitution exists so the project can finish that evolution coherently. diff --git a/Cargo.lock b/Cargo.lock index b5a929f..2950701 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,21 +23,10 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common 0.1.6", + "crypto-common", "generic-array", ] -[[package]] -name = "aead" -version = "0.6.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8202ab55fcbf46ca829833f347a82a2a4ce0596f0304ac322c2d100030cd56" -dependencies = [ - "bytes", - "crypto-common 0.2.0-rc.4", - "inout 0.2.1", -] - [[package]] name = "aes" version = "0.8.4" @@ -45,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", "zeroize", ] @@ -68,12 +57,6 @@ dependencies = [ "cc", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "amplify" version = "4.9.0" @@ -190,9 +173,6 @@ name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -dependencies = [ - "backtrace", -] [[package]] name = "argon2" @@ -206,12 +186,6 @@ dependencies = [ "password-hash 0.5.0", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" @@ -228,7 +202,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", + "derive_more", "educe", "fs-mistrust", "futures", @@ -329,19 +303,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-compat" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-compression" version = "0.4.33" @@ -436,17 +397,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version", -] - [[package]] name = "asynchronous-codec" version = "0.7.0" @@ -475,33 +425,12 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attohttpc" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" -dependencies = [ - "base64 0.22.1", - "http 1.3.1", - "log", - "url", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -608,17 +537,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", - "gloo-timers", - "tokio", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -640,18 +558,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base16ct" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" - -[[package]] -name = "base32" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" - [[package]] name = "base64" version = "0.21.7" @@ -755,20 +661,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "blake3" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq 0.3.1", + "digest", ] [[package]] @@ -791,16 +684,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.11.0-rc.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949" -dependencies = [ - "hybrid-array", - "zeroize", -] - [[package]] name = "bstr" version = "1.12.1" @@ -812,12 +695,6 @@ dependencies = [ "serde", ] -[[package]] -name = "btparse" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387e80962b798815a2b5c4bcfdb6bf626fa922ffe9f74e373103b858738e9f31" - [[package]] name = "bumpalo" version = "3.19.0" @@ -828,7 +705,7 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" name = "burrow" version = "0.1.0" dependencies = [ - "aead 0.5.2", + "aead", "anyhow", "argon2", "arti-client", @@ -853,10 +730,10 @@ dependencies = [ "ip_network", "ip_network_table", "ipnetwork", - "iroh", "libc", "libsystemd", "log", + "netstack-smoltcp", "nix 0.27.1", "once_cell", "parking_lot", @@ -968,12 +845,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cexpr" version = "0.6.0" @@ -1002,32 +873,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures", ] -[[package]] -name = "chacha20" -version = "0.10.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd162f2b8af3e0639d83f28a637e4e55657b7a74508dba5a9bf4da523d5c9e9" -dependencies = [ - "cfg-if", - "cipher 0.5.0-rc.1", - "cpufeatures", - "zeroize", -] - [[package]] name = "chacha20poly1305" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead 0.5.2", - "chacha20 0.9.1", - "cipher 0.4.4", - "poly1305 0.8.0", + "aead", + "chacha20", + "cipher", + "poly1305", "zeroize", ] @@ -1076,20 +935,8 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.6", - "inout 0.1.4", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e12a13eb01ded5d32ee9658d94f553a19e804204f2dc811df69ab4d9e0cb8c7" -dependencies = [ - "block-buffer 0.11.0-rc.5", - "crypto-common 0.2.0-rc.4", - "inout 0.2.1", + "crypto-common", + "inout", "zeroize", ] @@ -1155,42 +1002,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.16", -] - -[[package]] -name = "color-backtrace" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308329d5d62e877ba02943db3a8e8c052de9fde7ab48283395ba0e6494efbabd" -dependencies = [ - "backtrace", - "btparse", - "termcolor", -] - [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "compression-codecs" version = "0.4.32" @@ -1275,12 +1092,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-oid" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" - [[package]] name = "const-random" version = "0.1.18" @@ -1307,12 +1118,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "convert_case" version = "0.7.1" @@ -1331,16 +1136,6 @@ dependencies = [ "futures", ] -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1351,16 +1146,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1508,55 +1293,13 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" -dependencies = [ - "hybrid-array", - "rand_core 0.9.3", -] - -[[package]] -name = "crypto_box" -version = "0.10.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bda4de3e070830cf3a27a394de135b6709aefcc54d1e16f2f029271254a6ed9" -dependencies = [ - "aead 0.6.0-rc.2", - "chacha20 0.10.0-rc.2", - "crypto_secretbox", - "curve25519-dalek 5.0.0-pre.1", - "salsa20", - "serdect", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto_secretbox" -version = "0.2.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54532aae6546084a52cef855593daf9555945719eeeda9974150e0def854873e" -dependencies = [ - "aead 0.6.0-rc.2", - "chacha20 0.10.0-rc.2", - "cipher 0.5.0-rc.1", - "hybrid-array", - "poly1305 0.9.0-rc.2", - "salsa20", - "subtle", - "zeroize", -] - [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] @@ -1568,31 +1311,13 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto 0.2.9", + "digest", + "fiat-crypto", "rustc_version", "subtle", "zeroize", ] -[[package]] -name = "curve25519-dalek" -version = "5.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest 0.11.0-rc.3", - "fiat-crypto 0.3.0", - "rand_core 0.9.3", - "rustc_version", - "serde", - "subtle", - "zeroize", -] - [[package]] name = "curve25519-dalek-derive" version = "0.1.1" @@ -1713,25 +1438,55 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.16", +] + [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", - "zeroize", -] - -[[package]] -name = "der" -version = "0.8.0-rc.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d8dd2f26c86b27a2a8ea2767ec7f9df7a89516e4794e54ac01ee618dda3aa4" -dependencies = [ - "const-oid 0.10.1", - "pem-rfc7468 1.0.0-rc.3", + "const-oid", + "pem-rfc7468", "zeroize", ] @@ -1818,34 +1573,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", -] - [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl 2.0.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "unicode-xid", + "derive_more-impl", ] [[package]] @@ -1861,35 +1595,18 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "diatomic-waker" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.6", + "block-buffer", + "const-oid", + "crypto-common", "subtle", ] -[[package]] -name = "digest" -version = "0.11.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" -dependencies = [ - "block-buffer 0.11.0-rc.5", - "const-oid 0.10.1", - "crypto-common 0.2.0-rc.4", -] - [[package]] name = "directories" version = "6.0.0" @@ -1931,17 +1648,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "dlopen2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" -dependencies = [ - "libc", - "once_cell", - "winapi", -] - [[package]] name = "dlv-list" version = "0.5.2" @@ -1951,15 +1657,6 @@ dependencies = [ "const-random", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - [[package]] name = "dotenv" version = "0.15.0" @@ -1984,12 +1681,12 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der 0.7.10", - "digest 0.10.7", + "der", + "digest", "elliptic-curve", "rfc6979", - "signature 2.2.0", - "spki 0.7.3", + "signature", + "spki", ] [[package]] @@ -1998,19 +1695,8 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", -] - -[[package]] -name = "ed25519" -version = "3.0.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef49c0b20c0ad088893ad2a790a29c06a012b3f05bcfc66661fd22a94b32129" -dependencies = [ - "pkcs8 0.11.0-rc.7", - "serde", - "signature 3.0.0-rc.4", + "pkcs8", + "signature", ] [[package]] @@ -2019,28 +1705,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", + "curve25519-dalek", + "ed25519", "merlin", "rand_core 0.6.4", "serde", - "sha2 0.10.9", - "subtle", - "zeroize", -] - -[[package]] -name = "ed25519-dalek" -version = "3.0.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad207ed88a133091f83224265eac21109930db09bedcad05d5252f2af2de20a1" -dependencies = [ - "curve25519-dalek 5.0.0-pre.1", - "ed25519 3.0.0-rc.1", - "rand_core 0.9.3", - "serde", - "sha2 0.11.0-rc.2", - "signature 3.0.0-rc.4", + "sha2", "subtle", "zeroize", ] @@ -2069,31 +1739,19 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct 0.2.0", + "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", - "pkcs8 0.10.2", + "pkcs8", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - [[package]] name = "encode_unicode" version = "1.0.0" @@ -2183,6 +1841,15 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "etherparse" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d8a704b617484e9d867a0423cd45f7577f008c4068e2e33378f8d3860a6d73" +dependencies = [ + "arrayvec", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -2258,12 +1925,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "fiat-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" - [[package]] name = "figment" version = "0.10.19" @@ -2404,19 +2065,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-buffered" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" -dependencies = [ - "cordyceps", - "diatomic-waker", - "futures-core", - "pin-project-lite", - "spin 0.10.0", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -2450,19 +2098,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.31" @@ -2504,20 +2139,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows 0.61.3", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2599,18 +2220,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.13.0" @@ -2673,9 +2282,9 @@ dependencies = [ [[package]] name = "hash32" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] @@ -2707,8 +2316,6 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "allocator-api2", - "equivalent", "foldhash 0.2.0", ] @@ -2736,15 +2343,11 @@ dependencies = [ [[package]] name = "heapless" -version = "0.7.17" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "atomic-polyfill", "hash32", - "rustc_version", - "serde", - "spin 0.9.8", "stable_deref_trait", ] @@ -2767,52 +2370,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ "async-trait", - "bytes", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", - "h2 0.4.12", - "http 1.3.1", "idna", "ipnet", "once_cell", "rand 0.9.2", "ring", - "rustls", "thiserror 2.0.16", "tinyvec", "tokio", - "tokio-rustls", "tracing", "url", ] -[[package]] -name = "hickory-resolver" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", - "once_cell", - "parking_lot", - "rand 0.9.2", - "resolv-conf", - "rustls", - "smallvec", - "thiserror 2.0.16", - "tokio", - "tokio-rustls", - "tracing", -] - [[package]] name = "hkdf" version = "0.12.4" @@ -2828,7 +2403,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -2930,16 +2505,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hybrid-array" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" -dependencies = [ - "typenum", - "zeroize", -] - [[package]] name = "hyper" version = "0.14.32" @@ -3060,7 +2625,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -3209,27 +2774,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "igd-next" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" -dependencies = [ - "async-trait", - "attohttpc", - "bytes", - "futures", - "http 1.3.1", - "http-body-util", - "hyper 1.7.0", - "hyper-util", - "log", - "rand 0.9.2", - "tokio", - "url", - "xmltree", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -3282,15 +2826,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "inout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" -dependencies = [ - "hybrid-array", -] - [[package]] name = "insta" version = "1.43.2" @@ -3303,18 +2838,6 @@ dependencies = [ "similar", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "inventory" version = "0.3.24" @@ -3357,18 +2880,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2 0.5.10", - "widestring", - "windows-sys 0.48.0", - "winreg", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -3380,6 +2891,9 @@ name = "ipnetwork" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763" +dependencies = [ + "serde", +] [[package]] name = "iri-string" @@ -3391,213 +2905,6 @@ dependencies = [ "serde", ] -[[package]] -name = "iroh" -version = "0.94.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9428cef1eafd2eac584269986d1949e693877ac12065b401dfde69f664b07ac" -dependencies = [ - "aead 0.6.0-rc.2", - "backon", - "bytes", - "cfg_aliases", - "crypto_box", - "data-encoding", - "derive_more 2.0.1", - "ed25519-dalek 3.0.0-pre.1", - "futures-util", - "getrandom 0.3.3", - "hickory-resolver", - "http 1.3.1", - "igd-next", - "instant", - "iroh-base", - "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", - "iroh-quinn-udp", - "iroh-relay", - "n0-future", - "n0-snafu", - "n0-watcher", - "nested_enum_utils", - "netdev", - "netwatch", - "pin-project", - "pkarr", - "pkcs8 0.11.0-rc.7", - "portmapper", - "rand 0.9.2", - "reqwest 0.12.23", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "rustls-webpki", - "serde", - "smallvec", - "snafu", - "strum", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "wasm-bindgen-futures", - "webpki-roots", - "z32", -] - -[[package]] -name = "iroh-base" -version = "0.94.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db6dfffe81a58daae02b72c7784c20feef5b5d3849b190ed1c96a8fa0b3cae8" -dependencies = [ - "curve25519-dalek 5.0.0-pre.1", - "data-encoding", - "derive_more 2.0.1", - "ed25519-dalek 3.0.0-pre.1", - "n0-snafu", - "nested_enum_utils", - "rand_core 0.9.3", - "serde", - "snafu", - "url", - "zeroize", - "zeroize_derive", -] - -[[package]] -name = "iroh-metrics" -version = "0.36.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84c167b59ae22f940e78eb347ca5f02aa25608e994cb5a7cc016ac2d5eada18" -dependencies = [ - "iroh-metrics-derive", - "itoa", - "postcard", - "ryu", - "serde", - "snafu", - "tracing", -] - -[[package]] -name = "iroh-metrics-derive" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "748d380f26f7c25307c0a7acd181b84b977ddc2a1b7beece1e5998623c323aa1" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "iroh-quinn" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde160ebee7aabede6ae887460cd303c8b809054224815addf1469d54a6fcf7" -dependencies = [ - "bytes", - "cfg_aliases", - "iroh-quinn-proto", - "iroh-quinn-udp", - "pin-project-lite", - "rustc-hash 2.1.1", - "rustls", - "socket2 0.5.10", - "thiserror 2.0.16", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "iroh-quinn-proto" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" -dependencies = [ - "bytes", - "getrandom 0.2.16", - "rand 0.8.5", - "ring", - "rustc-hash 2.1.1", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.16", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "iroh-quinn-udp" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.5.10", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "iroh-relay" -version = "0.94.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360e201ab1803201de9a125dd838f7a4d13e6ba3a79aeb46c7fbf023266c062e" -dependencies = [ - "blake3", - "bytes", - "cfg_aliases", - "data-encoding", - "derive_more 2.0.1", - "getrandom 0.3.3", - "hickory-resolver", - "http 1.3.1", - "http-body-util", - "hyper 1.7.0", - "hyper-util", - "iroh-base", - "iroh-metrics", - "iroh-quinn", - "iroh-quinn-proto", - "lru 0.16.2", - "n0-future", - "n0-snafu", - "nested_enum_utils", - "num_enum", - "pin-project", - "pkarr", - "postcard", - "rand 0.9.2", - "reqwest 0.12.23", - "rustls", - "rustls-pki-types", - "serde", - "serde_bytes", - "sha1 0.11.0-rc.2", - "snafu", - "strum", - "tokio", - "tokio-rustls", - "tokio-util", - "tokio-websockets", - "tracing", - "url", - "webpki-roots", - "ws_stream_wasm", - "z32", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -3637,28 +2944,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "jobserver" version = "0.1.34" @@ -3716,7 +3001,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -3819,7 +3104,7 @@ dependencies = [ "nom 8.0.0", "once_cell", "serde", - "sha2 0.10.9", + "sha2", "thiserror 2.0.16", "uuid", ] @@ -3842,12 +3127,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - [[package]] name = "lock_api" version = "0.4.13" @@ -3864,40 +3143,18 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" - -[[package]] -name = "lru" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" -dependencies = [ - "hashbrown 0.16.1", -] - [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "matchers" version = "0.2.0" @@ -4014,75 +3271,12 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "moka" -version = "0.12.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "parking_lot", - "portable-atomic", - "rustc_version", - "smallvec", - "tagptr", - "uuid", -] - [[package]] name = "multimap" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -[[package]] -name = "n0-future" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439e746b307c1fd0c08771c3cafcd1746c3ccdb0d9c7b859d3caded366b6da76" -dependencies = [ - "cfg_aliases", - "derive_more 1.0.0", - "futures-buffered", - "futures-lite", - "futures-util", - "js-sys", - "pin-project", - "send_wrapper", - "tokio", - "tokio-util", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-time", -] - -[[package]] -name = "n0-snafu" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1815107e577a95bfccedb4cfabc73d709c0db6d12de3f14e0f284a8c5036dc4f" -dependencies = [ - "anyhow", - "btparse", - "color-backtrace", - "snafu", - "tracing-error", -] - -[[package]] -name = "n0-watcher" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34c65e127e06e5a2781b28df6a33ea474a7bddc0ac0cfea888bd20c79a1b6516" -dependencies = [ - "derive_more 2.0.1", - "n0-future", - "snafu", -] - [[package]] name = "native-tls" version = "0.2.14" @@ -4095,121 +3289,25 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] [[package]] -name = "nested_enum_utils" -version = "0.2.3" +name = "netstack-smoltcp" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d5475271bdd36a4a2769eac1ef88df0f99428ea43e52dfd8b0ee5cb674695f" +checksum = "ab8eb143b5f4a5907f5ac72a929edf6c9d9454485cf5a3a35ce8fd3c62165adf" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "netdev" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ab878b4c90faf36dab10ea51d48c69ae9019bcca47c048a7c9b273d5d7a823" -dependencies = [ - "dlopen2", - "ipnet", - "libc", - "netlink-packet-core", - "netlink-packet-route", - "netlink-sys", - "once_cell", - "system-configuration 0.6.1", - "windows-sys 0.59.0", -] - -[[package]] -name = "netlink-packet-core" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" -dependencies = [ - "paste", -] - -[[package]] -name = "netlink-packet-route" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" -dependencies = [ - "bitflags 2.9.4", - "libc", - "log", - "netlink-packet-core", -] - -[[package]] -name = "netlink-proto" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" -dependencies = [ - "bytes", + "etherparse", "futures", - "log", - "netlink-packet-core", - "netlink-sys", - "thiserror 2.0.16", -] - -[[package]] -name = "netlink-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" -dependencies = [ - "bytes", - "futures", - "libc", - "log", - "tokio", -] - -[[package]] -name = "netwatch" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d7ec7abdbfe67ee70af3f2002326491178419caea22254b9070e6ff0c83491" -dependencies = [ - "atomic-waker", - "bytes", - "cfg_aliases", - "derive_more 2.0.1", - "iroh-quinn-udp", - "js-sys", - "libc", - "n0-future", - "n0-watcher", - "nested_enum_utils", - "netdev", - "netlink-packet-core", - "netlink-packet-route", - "netlink-proto", - "netlink-sys", - "pin-project-lite", - "serde", - "snafu", - "socket2 0.6.3", - "time", + "rand 0.8.5", + "smoltcp", + "spin", "tokio", "tokio-util", "tracing", - "web-sys", - "windows 0.62.2", - "windows-result 0.4.1", - "wmi", ] [[package]] @@ -4234,6 +3332,7 @@ dependencies = [ "bitflags 2.9.4", "cfg-if", "libc", + "memoffset 0.9.1", ] [[package]] @@ -4309,21 +3408,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ntimestamp" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" -dependencies = [ - "base32", - "document-features", - "getrandom 0.2.16", - "httpdate", - "js-sys", - "once_cell", - "serde", -] - [[package]] name = "nu-ansi-term" version = "0.50.1" @@ -4569,7 +3653,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -4581,7 +3665,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -4590,12 +3674,12 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" dependencies = [ - "base16ct 0.2.0", + "base16ct", "ecdsa", "elliptic-curve", "primeorder", "rand_core 0.6.4", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -4671,10 +3755,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.7", + "digest", "hmac", "password-hash 0.4.2", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -4692,15 +3776,6 @@ dependencies = [ "base64ct", ] -[[package]] -name = "pem-rfc7468" -version = "1.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e58fab693c712c0d4e88f8eb3087b6521d060bcaf76aeb20cb192d809115ba" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -4717,16 +3792,6 @@ dependencies = [ "indexmap 2.11.4", ] -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version", -] - [[package]] name = "phf" version = "0.13.1" @@ -4802,46 +3867,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkarr" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "792c1328860f6874e90e3b387b4929819cc7783a6bd5a4728e918706eb436a48" -dependencies = [ - "async-compat", - "base32", - "bytes", - "cfg_aliases", - "document-features", - "dyn-clone", - "ed25519-dalek 3.0.0-pre.1", - "futures-buffered", - "futures-lite", - "getrandom 0.3.3", - "log", - "lru 0.13.0", - "ntimestamp", - "reqwest 0.12.23", - "self_cell", - "serde", - "sha1_smol", - "simple-dns", - "thiserror 2.0.16", - "tokio", - "tracing", - "url", - "wasm-bindgen-futures", -] - [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", + "der", + "pkcs8", + "spki", ] [[package]] @@ -4850,18 +3884,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.10", - "spki 0.7.3", -] - -[[package]] -name = "pkcs8" -version = "0.11.0-rc.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93eac55f10aceed84769df670ea4a32d2ffad7399400d41ee1c13b1cd8e1b478" -dependencies = [ - "der 0.8.0-rc.9", - "spki 0.8.0-rc.4", + "der", + "spki", ] [[package]] @@ -4912,17 +3936,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug", - "universal-hash 0.5.1", -] - -[[package]] -name = "poly1305" -version = "0.9.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb78a635f75d76d856374961deecf61031c0b6f928c83dc9c0924ab6c019c298" -dependencies = [ - "cpufeatures", - "universal-hash 0.6.0-rc.2", + "universal-hash", ] [[package]] @@ -4931,37 +3945,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -[[package]] -name = "portmapper" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73aa9bd141e0ff6060fea89a5437883f3b9ceea1cda71c790b90e17d072a3b3" -dependencies = [ - "base64 0.22.1", - "bytes", - "derive_more 2.0.1", - "futures-lite", - "futures-util", - "hyper-util", - "igd-next", - "iroh-metrics", - "libc", - "nested_enum_utils", - "netwatch", - "num_enum", - "rand 0.9.2", - "serde", - "smallvec", - "snafu", - "socket2 0.6.3", - "time", - "tokio", - "tokio-util", - "tower-layer", - "tracing", - "url", -] - [[package]] name = "postage" version = "0.5.0" @@ -4977,31 +3960,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "postcard-derive", - "serde", -] - -[[package]] -name = "postcard-derive" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "potential_utf" version = "0.1.3" @@ -5205,7 +4163,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.16", "tokio", "tracing", @@ -5242,7 +4200,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -5480,7 +4438,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration 0.5.1", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -5500,7 +4458,6 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-core", - "futures-util", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -5520,24 +4477,16 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-rustls", - "tokio-util", "tower 0.5.2", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "webpki-roots", ] -[[package]] -name = "resolv-conf" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" - [[package]] name = "retry-error" version = "0.11.0" @@ -5577,17 +4526,17 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", + "const-oid", + "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", - "pkcs8 0.10.2", + "pkcs8", "rand_core 0.6.4", - "sha2 0.10.9", - "signature 2.2.0", - "spki 0.7.3", + "sha2", + "signature", + "spki", "subtle", "zeroize", ] @@ -5696,7 +4645,6 @@ version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ - "log", "once_cell", "ring", "rustls-pki-types", @@ -5705,18 +4653,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework 3.5.1", -] - [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -5736,33 +4672,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework 3.5.1", - "security-framework-sys", - "webpki-root-certs 0.26.11", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.103.8" @@ -5792,23 +4701,13 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9f10dd250956c65d58a19507dd06ff976f898560fe843580d05134541f0898" dependencies = [ - "derive_more 2.0.1", + "derive_more", "educe", "either", "fluid-let", "thiserror 2.0.16", ] -[[package]] -name = "salsa20" -version = "0.11.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ff3b81c8a6e381bc1673768141383f9328048a60edddcfc752a8291a138443" -dependencies = [ - "cfg-if", - "cipher 0.5.0-rc.1", -] - [[package]] name = "same-file" version = "1.0.6" @@ -5890,12 +4789,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -5908,10 +4801,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.2.0", - "der 0.7.10", + "base16ct", + "der", "generic-array", - "pkcs8 0.10.2", + "pkcs8", "subtle", "zeroize", ] @@ -5923,20 +4816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5952,24 +4832,12 @@ dependencies = [ "libc", ] -[[package]] -name = "self_cell" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" - [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - [[package]] name = "serde" version = "1.0.228" @@ -5990,16 +4858,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -6126,16 +4984,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "serdect" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" -dependencies = [ - "base16ct 0.3.0", - "serde", -] - [[package]] name = "sha-1" version = "0.10.1" @@ -6144,7 +4992,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -6155,26 +5003,9 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] -[[package]] -name = "sha1" -version = "0.11.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e046edf639aa2e7afb285589e5405de2ef7e61d4b0ac1e30256e3eab911af9" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.11.0-rc.3", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.10.9" @@ -6183,18 +5014,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.11.0-rc.3", + "digest", ] [[package]] @@ -6203,7 +5023,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] @@ -6248,37 +5068,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", + "digest", "rand_core 0.6.4", ] -[[package]] -name = "signature" -version = "3.0.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc280a6ff65c79fbd6622f64d7127f32b85563bca8c53cd2e9141d6744a9056d" - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" -[[package]] -name = "simple-dns" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" -dependencies = [ - "bitflags 2.9.4", -] - [[package]] name = "siphasher" version = "1.0.2" @@ -6321,25 +5120,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "snafu" -version = "0.8.9" +name = "smoltcp" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" dependencies = [ - "backtrace", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.106", + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt 0.3.100", + "heapless", + "log", + "managed", ] [[package]] @@ -6371,12 +5163,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - [[package]] name = "spki" version = "0.7.3" @@ -6384,17 +5170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der 0.7.10", -] - -[[package]] -name = "spki" -version = "0.8.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" -dependencies = [ - "base64ct", - "der 0.8.0-rc.9", + "der", ] [[package]] @@ -6415,7 +5191,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" dependencies = [ - "cipher 0.4.4", + "cipher", "ssh-encoding", ] @@ -6426,8 +5202,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" dependencies = [ "base64ct", - "pem-rfc7468 0.7.0", - "sha2 0.10.9", + "pem-rfc7468", + "sha2", ] [[package]] @@ -6443,8 +5219,8 @@ dependencies = [ "rand_core 0.6.4", "rsa", "sec1", - "sha2 0.10.9", - "signature 2.2.0", + "sha2", + "signature", "ssh-cipher", "ssh-encoding", "subtle", @@ -6458,11 +5234,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" dependencies = [ "base64 0.21.7", - "digest 0.10.7", + "digest", "hex", "miette", "sha-1", - "sha2 0.10.9", + "sha2", "thiserror 1.0.69", "xxhash-rust", ] @@ -6587,19 +5363,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", + "core-foundation", + "system-configuration-sys", ] [[package]] @@ -6612,22 +5377,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - [[package]] name = "tap" version = "1.0.1" @@ -6647,15 +5396,6 @@ dependencies = [ "windows-sys 0.61.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -6851,7 +5591,6 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", ] [[package]] @@ -6864,33 +5603,10 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "futures-util", "pin-project-lite", "tokio", ] -[[package]] -name = "tokio-websockets" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6348ebfaaecd771cecb69e832961d277f59845d4220a584701f72728152b7" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-sink", - "getrandom 0.3.3", - "http 1.3.1", - "httparse", - "rand 0.9.2", - "ring", - "rustls-pki-types", - "simdutf8", - "tokio", - "tokio-rustls", - "tokio-util", -] - [[package]] name = "toml" version = "0.8.23" @@ -7076,7 +5792,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fac6e4d7e131b7d69bc85558383cd4ac61e46b4dd0d4ed51632f28fac98cac0c" dependencies = [ - "derive_more 2.0.1", + "derive_more", "hex", "itertools 0.14.0", "libc", @@ -7098,7 +5814,7 @@ checksum = "64454947258e49f238a5f06a06250a0c54598a1c7409898b5c79505e6a99e7af" dependencies = [ "bytes", "derive-deftly", - "digest 0.10.7", + "digest", "educe", "getrandom 0.4.2", "safelog", @@ -7119,7 +5835,7 @@ dependencies = [ "bytes", "caret", "derive-deftly", - "derive_more 2.0.1", + "derive_more", "educe", "itertools 0.14.0", "paste", @@ -7146,8 +5862,8 @@ checksum = "debc911738298ee801fce4577c36a50c55295b0bb9c5519461b83cc486a1f86e" dependencies = [ "caret", "derive_builder_fork_arti", - "derive_more 2.0.1", - "digest 0.10.7", + "derive_more", + "digest", "thiserror 2.0.16", "tor-bytes", "tor-checkable", @@ -7165,7 +5881,7 @@ dependencies = [ "caret", "cfg-if", "derive-deftly", - "derive_more 2.0.1", + "derive_more", "educe", "futures", "oneshot-fused-workaround", @@ -7202,7 +5918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b13a5b50bb55037f2e81b25dde42f420d57c75154216b8ef989006cea3ebee" dependencies = [ "humantime", - "signature 2.2.0", + "signature", "thiserror 2.0.16", "tor-llcrypto", ] @@ -7218,7 +5934,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", + "derive_more", "downcast-rs", "dyn-clone", "educe", @@ -7310,7 +6026,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bfa2b7b71c72830f61c48da4bb3e13191e0c0e1404b9c5168c795e4f5feb4a8" dependencies = [ - "digest 0.10.7", + "digest", "hex", "thiserror 2.0.16", "tor-llcrypto", @@ -7324,7 +6040,7 @@ checksum = "8ccd6fac844ac77c33ccdfcb56bf23ff40ebbb821ea708be79a481ec30e8c39c" dependencies = [ "async-compression", "base64ct", - "derive_more 2.0.1", + "derive_more", "futures", "hex", "http 1.3.1", @@ -7373,8 +6089,8 @@ dependencies = [ "async-trait", "base64ct", "derive_builder_fork_arti", - "derive_more 2.0.1", - "digest 0.10.7", + "derive_more", + "digest", "educe", "event-listener", "fs-mistrust", @@ -7394,7 +6110,7 @@ dependencies = [ "scopeguard", "serde", "serde_json", - "signature 2.2.0", + "signature", "static_assertions", "strum", "thiserror 2.0.16", @@ -7425,7 +6141,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595b005e6f571ac3890a34a00f361200aab781fd0218f2c528c86fc7af088df5" dependencies = [ - "derive_more 2.0.1", + "derive_more", "futures", "paste", "retry-error", @@ -7442,7 +6158,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727b8c8bc01c1587486055edab5c2cd0d5c960f5bb3fac796fc9911872b8b397" dependencies = [ - "derive_more 2.0.1", + "derive_more", "thiserror 2.0.16", "void", ] @@ -7457,7 +6173,7 @@ dependencies = [ "base64ct", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", + "derive_more", "dyn-clone", "educe", "futures", @@ -7498,8 +6214,8 @@ checksum = "a3693cd43f05cd01ac0aaa060dae5c5e53c4364f89e0d769e33cd629a2fd3118" dependencies = [ "data-encoding", "derive-deftly", - "derive_more 2.0.1", - "digest 0.10.7", + "derive_more", + "digest", "hex", "humantime", "itertools 0.14.0", @@ -7507,7 +6223,7 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "signature 2.2.0", + "signature", "subtle", "thiserror 2.0.16", "tor-basic-utils", @@ -7526,12 +6242,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ade9065ae49cfe2ab020ca9ca9f2b3c5c9b5fc0d8980fa681d8b3a0668e042f" dependencies = [ "derive-deftly", - "derive_more 2.0.1", + "derive_more", "downcast-rs", "paste", "rand 0.9.2", "rsa", - "signature 2.2.0", + "signature", "ssh-key", "thiserror 2.0.16", "tor-bytes", @@ -7552,7 +6268,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", + "derive_more", "downcast-rs", "dyn-clone", "fs-mistrust", @@ -7563,7 +6279,7 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "signature 2.2.0", + "signature", "ssh-key", "thiserror 2.0.16", "tor-basic-utils", @@ -7592,7 +6308,7 @@ dependencies = [ "caret", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", + "derive_more", "hex", "itertools 0.14.0", "safelog", @@ -7617,12 +6333,12 @@ dependencies = [ "aes", "base64ct", "ctr", - "curve25519-dalek 4.1.3", + "curve25519-dalek", "der-parser", "derive-deftly", - "derive_more 2.0.1", - "digest 0.10.7", - "ed25519-dalek 2.2.0", + "derive_more", + "digest", + "ed25519-dalek", "educe", "getrandom 0.4.2", "hex", @@ -7635,10 +6351,10 @@ dependencies = [ "rsa", "safelog", "serde", - "sha1 0.10.6", - "sha2 0.10.9", + "sha1", + "sha2", "sha3", - "signature 2.2.0", + "signature", "subtle", "thiserror 2.0.16", "tor-error", @@ -7671,7 +6387,7 @@ checksum = "599daea60fd3272eb72a795d1c593b45bbe15343cbc702340a81db124c06eed5" dependencies = [ "cfg-if", "derive-deftly", - "derive_more 2.0.1", + "derive_more", "dyn-clone", "educe", "futures", @@ -7714,7 +6430,7 @@ checksum = "41be8f47f521fc95206d2ba5facac8fb1a5b5b82169bd41ebeecdf46d1e77246" dependencies = [ "async-trait", "bitflags 2.9.4", - "derive_more 2.0.1", + "derive_more", "futures", "humantime", "itertools 0.14.0", @@ -7742,11 +6458,11 @@ checksum = "ea8bce73d2c78bd78a2a927336ca639cf6bd5d8ad092ebcd0b3fdeaa47dcc77e" dependencies = [ "amplify", "base64ct", - "cipher 0.4.4", + "cipher", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", - "digest 0.10.7", + "derive_more", + "digest", "educe", "enumset", "hex", @@ -7758,7 +6474,7 @@ dependencies = [ "saturating-time", "serde", "serde_with", - "signature 2.2.0", + "signature", "smallvec", "strum", "subtle", @@ -7784,7 +6500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507ab4b6a3d59ed0df5804eeed66dcacde75e3be13d3694216cdfdb666bce625" dependencies = [ "derive-deftly", - "derive_more 2.0.1", + "derive_more", "filetime", "fs-mistrust", "fslock", @@ -7817,13 +6533,13 @@ dependencies = [ "bytes", "caret", "cfg-if", - "cipher 0.4.4", + "cipher", "coarsetime", "criterion-cycles-per-byte", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.0.1", - "digest 0.10.7", + "derive_more", + "digest", "educe", "enum_dispatch", "futures", @@ -7891,7 +6607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7e57e9f71b22ae1df63dbccc8e428cb07feec0abd654735109fa563c10bbb90" dependencies = [ "derive-deftly", - "derive_more 2.0.1", + "derive_more", "humantime", "tor-cert", "tor-checkable", @@ -7928,7 +6644,7 @@ dependencies = [ "asynchronous-codec", "cfg-if", "coarsetime", - "derive_more 2.0.1", + "derive_more", "dyn-clone", "educe", "futures", @@ -7958,7 +6674,7 @@ dependencies = [ "assert_matches", "async-trait", "derive-deftly", - "derive_more 2.0.1", + "derive_more", "educe", "futures", "humantime", @@ -8001,7 +6717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da90e93b4b4aa4ec356ecbe9e19aced36fdd655e94ca459d1915120d873363f0" dependencies = [ "derive-deftly", - "derive_more 2.0.1", + "derive_more", "serde", "thiserror 2.0.16", "tor-memquota", @@ -8106,16 +6822,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-journald" version = "0.3.1" @@ -8297,17 +7003,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common 0.1.6", - "subtle", -] - -[[package]] -name = "universal-hash" -version = "0.6.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55be643b40a21558f44806b53ee9319595bc7ca6896372e4e08e5d7d83c9cd6" -dependencies = [ - "crypto-common 0.2.0-rc.4", + "crypto-common", "subtle", ] @@ -8333,7 +7029,6 @@ dependencies = [ "idna", "percent-encoding", "serde", - "serde_derive", ] [[package]] @@ -8354,7 +7049,6 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -8533,19 +7227,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmparser" version = "0.244.0" @@ -8584,24 +7265,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" -dependencies = [ - "webpki-root-certs 1.0.3", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "1.0.3" @@ -8675,23 +7338,11 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections 0.2.0", + "windows-collections", "windows-core 0.61.2", - "windows-future 0.2.1", + "windows-future", "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", + "windows-numerics", ] [[package]] @@ -8703,15 +7354,6 @@ dependencies = [ "windows-core 0.61.2", ] -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core 0.62.2", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -8746,18 +7388,7 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading 0.1.0", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-threading", ] [[package]] @@ -8804,16 +7435,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -8850,15 +7471,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -8904,21 +7516,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -8976,21 +7573,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -9009,12 +7591,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -9033,12 +7609,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -9069,12 +7639,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -9093,12 +7657,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -9117,12 +7675,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -9141,12 +7693,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -9284,46 +7830,12 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "wmi" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120d8c2b6a7c96c27bf4a7947fd7f02d73ca7f5958b8bd72a696e46cb5521ee6" -dependencies = [ - "chrono", - "futures", - "log", - "serde", - "thiserror 2.0.16", - "windows 0.62.2", - "windows-core 0.62.2", -] - [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" -[[package]] -name = "ws_stream_wasm" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version", - "send_wrapper", - "thiserror 2.0.16", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wyz" version = "0.5.1" @@ -9339,27 +7851,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.3", + "curve25519-dalek", "rand_core 0.6.4", "serde", "zeroize", ] -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - [[package]] name = "xxhash-rust" version = "0.8.15" @@ -9390,12 +7887,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "z32" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" - [[package]] name = "zerocopy" version = "0.8.27" @@ -9499,13 +7990,13 @@ dependencies = [ "aes", "byteorder", "bzip2", - "constant_time_eq 0.1.5", + "constant_time_eq", "crc32fast", "crossbeam-utils", "flate2", "hmac", "pbkdf2", - "sha1 0.10.6", + "sha1", "time", "zstd 0.11.2+zstd.1.5.2", ] diff --git a/Dockerfile b/Dockerfile index 404179b..3497e22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/library/rust:1.79-slim-bookworm AS builder +FROM docker.io/library/rust:1.85-slim-bookworm AS builder ARG TARGETPLATFORM ARG LLVM_VERSION=16 diff --git a/Makefile b/Makefile index 6563ab1..f927f5f 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,23 @@ tun := $(shell ifconfig -l | sed 's/ /\n/g' | grep utun | tail -n 1) -cargo_console := RUST_BACKTRACE=1 RUST_LOG=debug RUSTFLAGS='--cfg tokio_unstable' cargo run --all-features -cargo_norm := RUST_BACKTRACE=1 RUST_LOG=debug cargo run +cargo_console := env RUST_BACKTRACE=1 RUST_LOG=debug RUSTFLAGS='--cfg tokio_unstable' cargo run --all-features -- +cargo_norm := env RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- +sudo_cargo_console := sudo -E env RUST_BACKTRACE=1 RUST_LOG=debug RUSTFLAGS='--cfg tokio_unstable' cargo run --all-features -- +sudo_cargo_norm := sudo -E env RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- check: @cargo check build: - @cargo run build + @cargo build daemon-console: - @$(cargo_console) daemon + @$(sudo_cargo_console) daemon daemon: - @$(cargo_norm) daemon + @$(sudo_cargo_norm) daemon start: - @$(cargo_norm) start + @$(sudo_cargo_norm) start stop: @$(cargo_norm) stop diff --git a/README.md b/README.md index 89914d0..b8684c3 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,19 @@ Burrow is an open source tool for burrowing through firewalls, built by teenagers at [Hack Club](https://hackclub.com/). `burrow` provides a simple command-line tool to open virtual interfaces and direct traffic through them. +Routine verification now runs unprivileged with `cargo test --workspace --all-features`; only tunnel startup needs elevation. + +The repository now carries its own design and deployment record: + +- [Constitution](./CONSTITUTION.md) +- [Burrow Evolution](./evolution/README.md) +- [WireGuard Rust Lineage](./docs/WIREGUARD_LINEAGE.md) +- [Protocol Roadmap](./docs/PROTOCOL_ROADMAP.md) +- [Forward Email Runbook](./docs/FORWARDEMAIL.md) ## Contributing -Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow! Checkout [GETTING_STARTED.md](./docs/GETTING_STARTED.md) for build instructions and [GTK_APP.md](./docs/GTK_APP.md) for the Linux app. +Burrow is fully open source, you can fork the repo and start contributing easily. For more information and in-depth discussions, visit the `#burrow` channel on the [Hack Club Slack](https://hackclub.com/slack/), here you can ask for help and talk with other people interested in burrow. Checkout [GETTING_STARTED.md](./docs/GETTING_STARTED.md) for build instructions and [GTK_APP.md](./docs/GTK_APP.md) for the Linux app. Forge and deployment scaffolding live in [`flake.nix`](./flake.nix), [`nixos/`](./nixos), and [`.forgejo/workflows/`](./.forgejo/workflows/). Hosted mail backup operations live in [`docs/FORWARDEMAIL.md`](./docs/FORWARDEMAIL.md) and [`Tools/forwardemail-custom-s3.sh`](./Tools/forwardemail-custom-s3.sh). The project structure is divided in the following folders: diff --git a/Tools/forwardemail-custom-s3.sh b/Tools/forwardemail-custom-s3.sh new file mode 100755 index 0000000..5f39ddd --- /dev/null +++ b/Tools/forwardemail-custom-s3.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash + +set -euo pipefail +umask 077 + +usage() { + cat <<'EOF' +Usage: + Tools/forwardemail-custom-s3.sh \ + --domain burrow.net \ + --api-token-file intake/forwardemail_api_token.txt \ + --s3-endpoint https:// \ + --s3-region \ + --s3-bucket \ + --s3-access-key-file intake/hetzner-s3-user.txt \ + --s3-secret-key-file intake/hetzner-s3-secret.txt + +Options: + --domain Forward Email domain to update. + --api-token-file File containing the Forward Email API token. + --s3-endpoint S3-compatible endpoint URL. + --s3-region S3 region string expected by Forward Email. + --s3-bucket Bucket used for alias backup uploads. + --s3-access-key-file File containing the S3 access key id. + --s3-secret-key-file File containing the S3 secret access key. + --test-only Skip the update call and only test the saved connection. + --help Show this help text. + +Notes: + - Secrets are passed to curl through a temporary config file to avoid putting + them in the process list. + - By default the script updates the domain settings and then calls + /test-s3-connection. + - For Hetzner Object Storage, use the regional S3 endpoint such as + https://hel1.your-objectstorage.com, not an account alias endpoint. +EOF +} + +fail() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +require_file() { + local path="$1" + [[ -f "$path" ]] || fail "missing file: $path" +} + +read_secret() { + local path="$1" + local value + value="$(tr -d '\r\n' < "$path")" + [[ -n "$value" ]] || fail "empty secret file: $path" + printf '%s' "$value" +} + +domain="" +api_token_file="" +s3_endpoint="" +s3_region="" +s3_bucket="" +s3_access_key_file="" +s3_secret_key_file="" +test_only=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --domain) + domain="${2:-}" + shift 2 + ;; + --api-token-file) + api_token_file="${2:-}" + shift 2 + ;; + --s3-endpoint) + s3_endpoint="${2:-}" + shift 2 + ;; + --s3-region) + s3_region="${2:-}" + shift 2 + ;; + --s3-bucket) + s3_bucket="${2:-}" + shift 2 + ;; + --s3-access-key-file) + s3_access_key_file="${2:-}" + shift 2 + ;; + --s3-secret-key-file) + s3_secret_key_file="${2:-}" + shift 2 + ;; + --test-only) + test_only=true + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + fail "unknown argument: $1" + ;; + esac +done + +[[ -n "$domain" ]] || fail "--domain is required" +[[ -n "$api_token_file" ]] || fail "--api-token-file is required" +[[ -n "$s3_endpoint" || "$test_only" == true ]] || fail "--s3-endpoint is required unless --test-only is set" +[[ -n "$s3_region" || "$test_only" == true ]] || fail "--s3-region is required unless --test-only is set" +[[ -n "$s3_bucket" || "$test_only" == true ]] || fail "--s3-bucket is required unless --test-only is set" +[[ -n "$s3_access_key_file" || "$test_only" == true ]] || fail "--s3-access-key-file is required unless --test-only is set" +[[ -n "$s3_secret_key_file" || "$test_only" == true ]] || fail "--s3-secret-key-file is required unless --test-only is set" + +require_file "$api_token_file" +api_token="$(read_secret "$api_token_file")" + +if [[ "$test_only" == false ]]; then + require_file "$s3_access_key_file" + require_file "$s3_secret_key_file" + s3_access_key_id="$(read_secret "$s3_access_key_file")" + s3_secret_access_key="$(read_secret "$s3_secret_key_file")" + + case "$s3_endpoint" in + http://*|https://*) + ;; + *) + fail "--s3-endpoint must start with http:// or https://" + ;; + esac +fi + +curl_config="$(mktemp)" +trap 'rm -f "$curl_config"' EXIT + +if [[ "$test_only" == false ]]; then + cat >"$curl_config" <&2 + curl --config "$curl_config" + printf '\n' >&2 +fi + +cat >"$curl_config" <&2 +curl --config "$curl_config" +printf '\n' >&2 diff --git a/Tools/forwardemail-hetzner-storage.py b/Tools/forwardemail-hetzner-storage.py new file mode 100755 index 0000000..3a2a941 --- /dev/null +++ b/Tools/forwardemail-hetzner-storage.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import datetime as dt +import hashlib +import hmac +import sys +import textwrap +from pathlib import Path +from urllib.parse import urlencode, urlparse + +import requests + + +def read_secret(path: str) -> str: + value = Path(path).read_text(encoding="utf-8").strip() + if not value: + raise SystemExit(f"error: empty secret file: {path}") + return value + + +def sign(key: bytes, msg: str) -> bytes: + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + +def request( + *, + method: str, + endpoint: str, + region: str, + access_key: str, + secret_key: str, + bucket: str, + query: dict[str, str] | None = None, + body: bytes = b"", + content_type: str | None = None, +) -> requests.Response: + parsed = urlparse(endpoint) + if parsed.scheme != "https": + raise SystemExit("error: endpoint must use https") + + host = parsed.netloc + canonical_uri = f"/{bucket}" + query = query or {} + canonical_querystring = urlencode(sorted(query.items()), doseq=True, safe="~") + + now = dt.datetime.now(dt.timezone.utc) + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + payload_hash = hashlib.sha256(body).hexdigest() + + headers = { + "host": host, + "x-amz-content-sha256": payload_hash, + "x-amz-date": amz_date, + } + if content_type: + headers["content-type"] = content_type + + signed_headers = ";".join(sorted(headers.keys())) + canonical_headers = "".join(f"{name}:{headers[name]}\n" for name in sorted(headers.keys())) + canonical_request = "\n".join( + [ + method, + canonical_uri, + canonical_querystring, + canonical_headers, + signed_headers, + payload_hash, + ] + ) + + algorithm = "AWS4-HMAC-SHA256" + credential_scope = f"{date_stamp}/{region}/s3/aws4_request" + string_to_sign = "\n".join( + [ + algorithm, + amz_date, + credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(), + ] + ) + + k_date = sign(("AWS4" + secret_key).encode("utf-8"), date_stamp) + k_region = sign(k_date, region) + k_service = sign(k_region, "s3") + signing_key = sign(k_service, "aws4_request") + signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() + + auth_header = ( + f"{algorithm} Credential={access_key}/{credential_scope}, " + f"SignedHeaders={signed_headers}, Signature={signature}" + ) + + url = f"{parsed.scheme}://{host}{canonical_uri}" + if canonical_querystring: + url = f"{url}?{canonical_querystring}" + + response = requests.request( + method, + url, + headers={**headers, "Authorization": auth_header}, + data=body, + timeout=30, + ) + return response + + +def ensure_bucket(args: argparse.Namespace, bucket: str) -> None: + head = request( + method="HEAD", + endpoint=args.endpoint, + region=args.region, + access_key=args.access_key, + secret_key=args.secret_key, + bucket=bucket, + ) + if head.status_code == 200: + print(f"{bucket}: exists") + return + if head.status_code != 404: + raise SystemExit(f"error: HEAD {bucket} returned {head.status_code}: {head.text[:200]}") + + body = textwrap.dedent( + f"""\ + + + {args.region} + + """ + ).encode("utf-8") + create = request( + method="PUT", + endpoint=args.endpoint, + region=args.region, + access_key=args.access_key, + secret_key=args.secret_key, + bucket=bucket, + body=body, + content_type="application/xml", + ) + if create.status_code not in (200, 204): + raise SystemExit(f"error: PUT {bucket} returned {create.status_code}: {create.text[:200]}") + print(f"{bucket}: created") + + +def put_lifecycle(args: argparse.Namespace, bucket: str) -> None: + body = textwrap.dedent( + f"""\ + + + + expire-forwardemail-backups-after-{args.expire_days}-days + Enabled + + + + + {args.expire_days} + + + + """ + ).encode("utf-8") + response = request( + method="PUT", + endpoint=args.endpoint, + region=args.region, + access_key=args.access_key, + secret_key=args.secret_key, + bucket=bucket, + query={"lifecycle": ""}, + body=body, + content_type="application/xml", + ) + if response.status_code not in (200, 204): + raise SystemExit( + f"error: PUT lifecycle for {bucket} returned {response.status_code}: {response.text[:200]}" + ) + print(f"{bucket}: lifecycle set to {args.expire_days} days") + + +def get_lifecycle(args: argparse.Namespace, bucket: str) -> None: + response = request( + method="GET", + endpoint=args.endpoint, + region=args.region, + access_key=args.access_key, + secret_key=args.secret_key, + bucket=bucket, + query={"lifecycle": ""}, + ) + if response.status_code != 200: + raise SystemExit( + f"error: GET lifecycle for {bucket} returned {response.status_code}: {response.text[:200]}" + ) + print(f"=== {bucket} lifecycle ===") + print(response.text.strip()) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Provision Hetzner object-storage buckets for Forward Email backups." + ) + parser.add_argument( + "--endpoint", + default="https://hel1.your-objectstorage.com", + help="Public S3-compatible endpoint URL. For Hetzner, use the regional endpoint, not the account alias.", + ) + parser.add_argument("--region", default="hel1", help="S3 region.") + parser.add_argument( + "--access-key-file", + default="intake/hetzner-s3-user.txt", + help="File containing the S3 access key id.", + ) + parser.add_argument( + "--secret-key-file", + default="intake/hetzner-s3-secret.txt", + help="File containing the S3 secret key.", + ) + parser.add_argument( + "--bucket", + action="append", + required=True, + help="Bucket to provision. Repeat for multiple buckets.", + ) + parser.add_argument( + "--expire-days", + type=int, + default=90, + help="Lifecycle expiry window in days.", + ) + parser.add_argument( + "--verify-only", + action="store_true", + help="Skip create/update and only read the current lifecycle.", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + args.access_key = read_secret(args.access_key_file) + args.secret_key = read_secret(args.secret_key_file) + + for bucket in args.bucket: + if args.verify_only: + get_lifecycle(args, bucket) + continue + ensure_bucket(args, bucket) + put_lifecycle(args, bucket) + get_lifecycle(args, bucket) + + +if __name__ == "__main__": + try: + main() + except requests.RequestException as err: + raise SystemExit(f"error: request failed: {err}") from err diff --git a/burrow/src/daemon/net/unix.rs b/burrow/src/daemon/net/unix.rs index 975c470..f7f9433 100644 --- a/burrow/src/daemon/net/unix.rs +++ b/burrow/src/daemon/net/unix.rs @@ -11,11 +11,7 @@ use tokio::{ use tracing::{debug, error, info}; use crate::daemon::rpc::{ - DaemonCommand, - DaemonMessage, - DaemonNotification, - DaemonRequest, - DaemonResponse, + DaemonCommand, DaemonMessage, DaemonNotification, DaemonRequest, DaemonResponse, DaemonResponseData, }; diff --git a/burrow/src/daemon/rpc/client.rs b/burrow/src/daemon/rpc/client.rs index 862e34c..06a9b45 100644 --- a/burrow/src/daemon/rpc/client.rs +++ b/burrow/src/daemon/rpc/client.rs @@ -1,5 +1,6 @@ use anyhow::Result; use hyper_util::rt::TokioIo; +use std::path::Path; use tokio::net::UnixStream; use tonic::transport::{Endpoint, Uri}; use tower::service_fn; @@ -15,10 +16,18 @@ pub struct BurrowClient { impl BurrowClient { #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub async fn from_uds() -> Result { + Self::from_uds_path(get_socket_path()).await + } + + #[cfg(any(target_os = "linux", target_vendor = "apple"))] + pub async fn from_uds_path(path: impl AsRef) -> Result { + let socket_path = path.as_ref().to_owned(); 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?)) + .connect_with_connector(service_fn(move |_: Uri| { + let socket_path = socket_path.clone(); + async move { + Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(&socket_path).await?)) + } })) .await?; let nw_client = NetworksClient::new(channel.clone()); diff --git a/burrow/src/daemon/rpc/request.rs b/burrow/src/daemon/rpc/request.rs index e9480aa..91562cc 100644 --- a/burrow/src/daemon/rpc/request.rs +++ b/burrow/src/daemon/rpc/request.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use tun::TunOptions; #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(tag="method", content="params")] +#[serde(tag = "method", content = "params")] pub enum DaemonCommand { Start(DaemonStartOptions), ServerInfo, diff --git a/burrow/src/lib.rs b/burrow/src/lib.rs index bbc8c17..15b6a19 100644 --- a/burrow/src/lib.rs +++ b/burrow/src/lib.rs @@ -10,11 +10,11 @@ mod auth; mod daemon; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub mod database; -#[cfg(any(target_os = "linux", target_vendor = "apple"))] -pub mod mesh; #[cfg(target_os = "linux")] pub mod tor; pub(crate) mod tracing; +#[cfg(target_os = "linux")] +pub mod usernet; #[cfg(target_vendor = "apple")] pub use daemon::apple::spawn_in_process; diff --git a/burrow/src/main.rs b/burrow/src/main.rs index c1f512b..c91f36f 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -11,11 +11,10 @@ mod wireguard; #[cfg(any(target_os = "linux", target_vendor = "apple"))] mod auth; - -#[cfg(any(target_os = "linux", target_vendor = "apple"))] -mod mesh; #[cfg(target_os = "linux")] mod tor; +#[cfg(target_os = "linux")] +mod usernet; #[cfg(any(target_os = "linux", target_vendor = "apple"))] use daemon::{DaemonClient, DaemonCommand}; @@ -74,6 +73,9 @@ enum Commands { /// Delete Network NetworkDelete(NetworkDeleteArgs), #[cfg(target_os = "linux")] + /// Run a command in an unshared Linux namespace using a Burrow backend + Exec(ExecArgs), + #[cfg(target_os = "linux")] /// Run a command in a Linux user namespace with Tor-backed networking TorExec(TorExecArgs), } @@ -116,6 +118,17 @@ struct TorExecArgs { command: Vec, } +#[cfg(target_os = "linux")] +#[derive(Args)] +struct ExecArgs { + #[arg(long, value_enum)] + backend: usernet::ExecBackendKind, + #[arg(long)] + payload: Option, + #[arg(required = true, num_args = 1.., trailing_var_arg = true)] + command: Vec, +} + #[cfg(any(target_os = "linux", target_vendor = "apple"))] async fn try_start() -> Result<()> { let mut client = BurrowClient::from_uds().await?; @@ -229,9 +242,30 @@ async fn try_network_delete(id: i32) -> Result<()> { #[cfg(target_os = "linux")] async fn try_tor_exec(payload_path: &str, command: Vec) -> Result<()> { - let payload = tokio::fs::read(payload_path).await?; - let config = tor::Config::from_payload(&payload)?; - let exit_code = tor::run_exec(config, command).await?; + let exit_code = usernet::run_exec(usernet::ExecInvocation { + backend: usernet::ExecBackendKind::Tor, + payload_path: Some(payload_path.into()), + command, + }) + .await?; + if exit_code != 0 { + std::process::exit(exit_code); + } + Ok(()) +} + +#[cfg(target_os = "linux")] +async fn try_exec( + backend: usernet::ExecBackendKind, + payload: Option, + command: Vec, +) -> Result<()> { + let exit_code = usernet::run_exec(usernet::ExecInvocation { + backend, + payload_path: payload.map(Into::into), + command, + }) + .await?; if exit_code != 0 { std::process::exit(exit_code); } @@ -315,6 +349,15 @@ async fn main() -> Result<()> { Commands::NetworkReorder(args) => try_network_reorder(args.id, args.index).await?, Commands::NetworkDelete(args) => try_network_delete(args.id).await?, #[cfg(target_os = "linux")] + Commands::Exec(args) => { + try_exec( + args.backend.clone(), + args.payload.clone(), + args.command.clone(), + ) + .await? + } + #[cfg(target_os = "linux")] Commands::TorExec(args) => try_tor_exec(&args.payload_path, args.command.clone()).await?, } diff --git a/burrow/src/tor/dns.rs b/burrow/src/tor/dns.rs index 46ba96e..d918fc4 100644 --- a/burrow/src/tor/dns.rs +++ b/burrow/src/tor/dns.rs @@ -76,13 +76,10 @@ pub async fn spawn( } }); - Ok(TorDnsHandle { - shutdown: shutdown_tx, - task, - }) + Ok(TorDnsHandle { shutdown: shutdown_tx, task }) } -async fn build_response( +pub(crate) async fn build_response( packet: &[u8], tor_client: &TorClient, ) -> Result> { @@ -133,9 +130,11 @@ fn record_for_address( addr: IpAddr, ) -> Option { match (record_type, addr) { - (RecordType::A, IpAddr::V4(ip)) => { - Some(Record::from_rdata(name, DNS_TTL_SECS, RData::A(A::from(ip)))) - } + (RecordType::A, IpAddr::V4(ip)) => Some(Record::from_rdata( + name, + DNS_TTL_SECS, + RData::A(A::from(ip)), + )), (RecordType::AAAA, IpAddr::V6(ip)) => Some(Record::from_rdata( name, DNS_TTL_SECS, diff --git a/burrow/src/tor/mod.rs b/burrow/src/tor/mod.rs index 3c936f7..635c355 100644 --- a/burrow/src/tor/mod.rs +++ b/burrow/src/tor/mod.rs @@ -1,5 +1,5 @@ mod config; -mod dns; +pub(crate) mod dns; mod exec; mod runtime; mod system; diff --git a/burrow/src/tor/runtime.rs b/burrow/src/tor/runtime.rs index e583b83..45690ee 100644 --- a/burrow/src/tor/runtime.rs +++ b/burrow/src/tor/runtime.rs @@ -118,10 +118,7 @@ pub async fn spawn_with_client( }), }; - Ok(TorHandle { - shutdown: shutdown_tx, - task, - }) + Ok(TorHandle { shutdown: shutdown_tx, task }) } fn join_error(err: JoinError) -> anyhow::Error { diff --git a/burrow/src/tor/system.rs b/burrow/src/tor/system.rs index db00e3c..74f8157 100644 --- a/burrow/src/tor/system.rs +++ b/burrow/src/tor/system.rs @@ -118,7 +118,10 @@ mod tests { }; let parsed = socket_addr_from_storage(&storage, size_of::()).unwrap(); - assert_eq!(parsed, SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9040))); + assert_eq!( + parsed, + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9040)) + ); } #[test] diff --git a/burrow/src/usernet/mod.rs b/burrow/src/usernet/mod.rs new file mode 100644 index 0000000..12de810 --- /dev/null +++ b/burrow/src/usernet/mod.rs @@ -0,0 +1,935 @@ +use std::{ + collections::HashMap, + env, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + os::fd::{AsRawFd, FromRawFd, RawFd}, + os::unix::net::UnixStream as StdUnixStream, + os::unix::process::ExitStatusExt, + path::{Path, PathBuf}, + process::{Command as StdCommand, ExitStatus}, + str, + sync::Arc, + time::Duration, +}; + +use anyhow::{anyhow, bail, Context, Result}; +use clap::ValueEnum; +use futures::{SinkExt, StreamExt}; +use ipnetwork::IpNetwork; +use netstack_smoltcp::{ + StackBuilder, TcpListener as StackTcpListener, TcpStream as StackTcpStream, + UdpSocket as StackUdpSocket, +}; +use nix::{ + cmsg_space, + fcntl::{fcntl, FcntlArg, FdFlag}, + sys::socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags}, +}; +use serde::{Deserialize, Serialize}; +use tokio::{ + io::copy_bidirectional, + net::{TcpStream, UdpSocket}, + process::{Child, Command}, + sync::{mpsc, Mutex, RwLock}, + task::JoinSet, +}; +use tokio_util::compat::FuturesAsyncReadCompatExt; +use tracing::{debug, warn}; +use tun::{tokio::TunInterface as TokioTunInterface, TunOptions}; + +use crate::{ + tor::{bootstrap_client, dns::build_response as build_tor_dns_response, Config as TorConfig}, + wireguard::{Config as WireGuardConfig, Interface as WireGuardInterface}, +}; + +const INNER_ENV: &str = "BURROW_USERNET_INNER"; +const INNER_CONTROL_FD_ENV: &str = "BURROW_USERNET_CONTROL_FD"; +const INNER_TUN_CONFIG_ENV: &str = "BURROW_USERNET_TUN_CONFIG"; +const DEFAULT_MTU: u32 = 1500; +const DEFAULT_TUN_V4: &str = "100.64.0.2/24"; +const DEFAULT_TUN_V6: &str = "fd00:64::2/64"; +const UDP_IDLE_TIMEOUT: Duration = Duration::from_secs(30); +const READY_ACK: &[u8; 1] = b"1"; + +#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)] +pub enum ExecBackendKind { + Direct, + Tor, + Wireguard, +} + +impl ExecBackendKind { + fn cli_name(&self) -> &'static str { + match self { + Self::Direct => "direct", + Self::Tor => "tor", + Self::Wireguard => "wireguard", + } + } +} + +#[derive(Clone, Debug)] +pub struct ExecInvocation { + pub backend: ExecBackendKind, + pub payload_path: Option, + pub command: Vec, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DirectConfig { + #[serde(default)] + pub address: Vec, + #[serde(default)] + pub dns: Vec, + #[serde(default)] + pub mtu: Option, + #[serde(default)] + pub tun_name: Option, +} + +impl DirectConfig { + pub fn from_payload(payload: &[u8]) -> Result { + if payload.is_empty() { + return Ok(Self::default()); + } + + if let Ok(config) = serde_json::from_slice(payload) { + return Ok(config); + } + + let payload = str::from_utf8(payload).context("direct payload must be valid UTF-8")?; + toml::from_str(payload).context("failed to parse direct payload as JSON or TOML") + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct TunNetworkConfig { + tun_name: String, + addresses: Vec, + mtu: u32, +} + +enum PreparedBackend { + Socket { + backend: SocketBackend, + tun_config: TunNetworkConfig, + }, + Wireguard { + config: WireGuardConfig, + tun_config: TunNetworkConfig, + }, +} + +impl PreparedBackend { + fn tun_config(&self) -> &TunNetworkConfig { + match self { + Self::Socket { tun_config, .. } => tun_config, + Self::Wireguard { tun_config, .. } => tun_config, + } + } +} + +struct NamespaceChild { + child: Child, + control: StdUnixStream, +} + +#[derive(Clone)] +enum SocketBackend { + Direct, + Tor(Arc>), +} + +#[derive(Debug)] +struct UdpReply { + payload: Vec, + source: SocketAddr, + destination: SocketAddr, +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +struct UdpFlowKey { + local: SocketAddr, + remote: SocketAddr, +} + +pub async fn run_exec(invocation: ExecInvocation) -> Result { + if invocation.command.is_empty() { + bail!("exec requires a command to run"); + } + + if env::var_os(INNER_ENV).is_some() { + run_inner(invocation.command).await + } else { + run_supervisor(invocation).await + } +} + +async fn run_supervisor(invocation: ExecInvocation) -> Result { + let prepared = prepare_backend(&invocation).await?; + let mut child = spawn_namespaced_child(&invocation, prepared.tun_config())?; + let tun = child.receive_tun().await?; + + match prepared { + PreparedBackend::Socket { backend, .. } => run_socket_backend(backend, tun, child).await, + PreparedBackend::Wireguard { config, .. } => { + run_wireguard_backend(config, tun, child).await + } + } +} + +async fn prepare_backend(invocation: &ExecInvocation) -> Result { + match invocation.backend { + ExecBackendKind::Direct => { + let payload = read_optional_payload(invocation.payload_path.as_deref()).await?; + let config = DirectConfig::from_payload(&payload)?; + let tun_config = socket_tun_config( + &config.address, + config.mtu, + config.tun_name.as_deref(), + "burrow-direct", + )?; + Ok(PreparedBackend::Socket { + backend: SocketBackend::Direct, + tun_config, + }) + } + ExecBackendKind::Tor => { + let payload = read_required_payload(invocation.payload_path.as_deref(), "tor").await?; + let mut config = TorConfig::from_payload(&payload)?; + let (state_dir, cache_dir) = config.runtime_dirs(std::process::id() as i32); + config.arti.state_dir = state_dir; + config.arti.cache_dir = cache_dir; + let tun_config = socket_tun_config( + &config.address, + config.mtu, + config.tun_name.as_deref(), + "burrow-tor", + )?; + let tor_client = bootstrap_client(&config).await?; + Ok(PreparedBackend::Socket { + backend: SocketBackend::Tor(tor_client), + tun_config, + }) + } + ExecBackendKind::Wireguard => { + let payload = + read_required_payload(invocation.payload_path.as_deref(), "wireguard").await?; + let config = parse_wireguard_payload(&payload, invocation.payload_path.as_deref())?; + let tun_config = wireguard_tun_config(&config)?; + Ok(PreparedBackend::Wireguard { config, tun_config }) + } + } +} + +fn spawn_namespaced_child( + invocation: &ExecInvocation, + tun_config: &TunNetworkConfig, +) -> Result { + ensure_tool("unshare")?; + ensure_tool("ip")?; + + let (parent_control, child_control) = + StdUnixStream::pair().context("failed to create namespace control socket")?; + set_inheritable(child_control.as_raw_fd())?; + + let current_exe = env::current_exe().context("failed to locate current burrow binary")?; + let mut cmd = Command::new("unshare"); + cmd.args([ + "--user", + "--map-root-user", + "--net", + "--mount", + "--pid", + "--fork", + "--kill-child", + "--mount-proc", + ]); + cmd.env(INNER_ENV, "1"); + cmd.env(INNER_CONTROL_FD_ENV, child_control.as_raw_fd().to_string()); + cmd.env( + INNER_TUN_CONFIG_ENV, + serde_json::to_string(tun_config).context("failed to encode namespace tun config")?, + ); + cmd.arg(current_exe); + cmd.arg("exec"); + cmd.args(["--backend", invocation.backend.cli_name()]); + if let Some(payload_path) = &invocation.payload_path { + cmd.arg("--payload"); + cmd.arg(payload_path); + } + cmd.arg("--"); + cmd.args(&invocation.command); + + let child = cmd + .spawn() + .context("failed to enter unshared Linux namespace")?; + drop(child_control); + + Ok(NamespaceChild { child, control: parent_control }) +} + +async fn run_inner(command: Vec) -> Result { + run_ip(["link", "set", "lo", "up"])?; + let tun_config = read_inner_tun_config()?; + let tun = open_tun_device(&tun_config)?; + configure_tun_addresses(&tun, &tun_config.addresses, tun_config.mtu)?; + let name = tun.name().context("failed to retrieve tun device name")?; + run_ip(["link", "set", "dev", &name, "up"])?; + install_default_routes(&name, &tun_config.addresses)?; + + let control_fd = env::var(INNER_CONTROL_FD_ENV) + .context("missing namespace control fd")? + .parse::() + .context("invalid namespace control fd")?; + send_tun_fd(control_fd, tun.as_raw_fd())?; + await_parent_ready(control_fd).await?; + drop(tun); + + let status = spawn_child(&command).await?; + child_exit_code(status) +} + +impl NamespaceChild { + async fn receive_tun(&mut self) -> Result { + let control = self + .control + .try_clone() + .context("failed to clone namespace control socket")?; + let fd = tokio::task::spawn_blocking(move || recv_tun_fd(&control)) + .await + .context("failed to join namespace tun receive task")??; + tokio_tun_from_fd(fd) + } + + async fn signal_ready(&self) -> Result<()> { + let mut control = self + .control + .try_clone() + .context("failed to clone namespace control socket")?; + tokio::task::spawn_blocking(move || -> Result<()> { + std::io::Write::write_all(&mut control, READY_ACK) + .context("failed to acknowledge namespace readiness")?; + Ok(()) + }) + .await + .context("failed to join namespace ready task")??; + Ok(()) + } + + async fn wait(mut self) -> Result { + self.child + .wait() + .await + .context("failed to wait for namespace child") + } +} + +async fn run_socket_backend( + backend: SocketBackend, + tun: TokioTunInterface, + child: NamespaceChild, +) -> Result { + let tun = Arc::new(tun); + let (stack, runner, udp_socket, tcp_listener) = StackBuilder::default() + .stack_buffer_size(1024) + .udp_buffer_size(1024) + .tcp_buffer_size(1024) + .enable_udp(true) + .enable_tcp(true) + .enable_icmp(true) + .build() + .context("failed to build userspace netstack")?; + let (mut stack_sink, mut stack_stream) = stack.split(); + + let mut tasks = JoinSet::new(); + if let Some(runner) = runner { + tasks.spawn(async move { runner.await.map_err(anyhow::Error::from) }); + } + + { + let tun = tun.clone(); + tasks.spawn(async move { + let mut buf = vec![0u8; 65_535]; + loop { + let len = tun + .recv(&mut buf) + .await + .context("failed to read packet from tun")?; + if len == 0 { + continue; + } + stack_sink + .send(buf[..len].to_vec()) + .await + .context("failed to send tun packet into userspace stack")?; + } + #[allow(unreachable_code)] + Result::<()>::Ok(()) + }); + } + + { + let tun = tun.clone(); + tasks.spawn(async move { + while let Some(packet) = stack_stream.next().await { + let packet = packet.context("failed to receive packet from userspace stack")?; + tun.send(&packet) + .await + .context("failed to write userspace stack packet to tun")?; + } + Result::<()>::Ok(()) + }); + } + + if let Some(tcp_listener) = tcp_listener { + let backend = backend.clone(); + tasks.spawn(async move { tcp_dispatch_loop(tcp_listener, backend).await }); + } + + if let Some(udp_socket) = udp_socket { + tasks.spawn(async move { udp_dispatch_loop(udp_socket, backend).await }); + } + + child.signal_ready().await?; + let status = child.wait().await?; + + tasks.abort_all(); + while let Some(joined) = tasks.join_next().await { + match joined { + Ok(Ok(())) => {} + Ok(Err(err)) => debug!(?err, "usernet background task exited with error"), + Err(err) if err.is_cancelled() => {} + Err(err) => debug!(?err, "usernet background task panicked"), + } + } + + child_exit_code(status) +} + +async fn run_wireguard_backend( + config: WireGuardConfig, + tun: TokioTunInterface, + child: NamespaceChild, +) -> Result { + let interface: WireGuardInterface = config.try_into()?; + interface.set_tun(tun).await; + let interface = Arc::new(interface); + let runner = { + let interface = interface.clone(); + tokio::spawn(async move { interface.run().await }) + }; + + child.signal_ready().await?; + let status = child.wait().await?; + + interface.remove_tun().await; + match runner.await { + Ok(Ok(())) => {} + Ok(Err(err)) => debug!(?err, "wireguard exec runtime exited with error"), + Err(err) if err.is_cancelled() => {} + Err(err) => debug!(?err, "wireguard exec runtime panicked"), + } + + child_exit_code(status) +} + +async fn tcp_dispatch_loop(mut listener: StackTcpListener, backend: SocketBackend) -> Result<()> { + let mut tasks = JoinSet::new(); + loop { + tokio::select! { + Some(result) = tasks.join_next(), if !tasks.is_empty() => { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => warn!(?err, "tcp bridge task failed"), + Err(err) if err.is_cancelled() => {} + Err(err) => warn!(?err, "tcp bridge task panicked"), + } + } + next = listener.next() => match next { + Some((stream, local_addr, remote_addr)) => { + debug!(%local_addr, %remote_addr, "accepted userspace tcp stream"); + let backend = backend.clone(); + tasks.spawn(async move { + bridge_tcp(backend, stream, local_addr, remote_addr).await + }); + } + None => break, + } + } + } + + tasks.abort_all(); + while let Some(result) = tasks.join_next().await { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => debug!(?err, "tcp bridge task exited during shutdown"), + Err(err) if err.is_cancelled() => {} + Err(err) => debug!(?err, "tcp bridge task panicked during shutdown"), + } + } + Ok(()) +} + +async fn bridge_tcp( + backend: SocketBackend, + mut inbound: StackTcpStream, + _local_addr: SocketAddr, + remote_addr: SocketAddr, +) -> Result<()> { + match backend { + SocketBackend::Direct => { + debug!(%remote_addr, "dialing direct outbound tcp"); + let mut outbound = TcpStream::connect(remote_addr) + .await + .with_context(|| format!("failed to connect to {remote_addr}"))?; + copy_bidirectional(&mut inbound, &mut outbound) + .await + .with_context(|| format!("failed to bridge tcp stream for {remote_addr}"))?; + } + SocketBackend::Tor(tor_client) => { + debug!(%remote_addr, "dialing tor outbound tcp"); + let tor_stream = tor_client + .connect((remote_addr.ip().to_string(), remote_addr.port())) + .await + .with_context(|| format!("failed to connect to {remote_addr} over tor"))?; + let mut tor_stream = tor_stream.compat(); + copy_bidirectional(&mut inbound, &mut tor_stream) + .await + .with_context(|| format!("failed to bridge tor stream for {remote_addr}"))?; + } + } + Ok(()) +} + +async fn udp_dispatch_loop(socket: StackUdpSocket, backend: SocketBackend) -> Result<()> { + let (mut udp_reader, mut udp_writer) = socket.split(); + let (reply_tx, mut reply_rx) = mpsc::channel::(128); + let direct_sessions = Arc::new(Mutex::new( + HashMap::>>::new(), + )); + let mut session_tasks = JoinSet::new(); + + loop { + tokio::select! { + Some(result) = session_tasks.join_next(), if !session_tasks.is_empty() => { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => warn!(?err, "udp session task failed"), + Err(err) if err.is_cancelled() => {} + Err(err) => warn!(?err, "udp session task panicked"), + } + } + maybe_reply = reply_rx.recv() => match maybe_reply { + Some(reply) => { + udp_writer + .send((reply.payload, reply.source, reply.destination)) + .await + .context("failed to write udp reply into userspace stack")?; + } + None => break, + }, + maybe_datagram = udp_reader.next() => match maybe_datagram { + Some((payload, local_addr, remote_addr)) => { + match &backend { + SocketBackend::Direct => { + dispatch_direct_udp( + payload, + local_addr, + remote_addr, + reply_tx.clone(), + direct_sessions.clone(), + &mut session_tasks, + ).await?; + } + SocketBackend::Tor(tor_client) => { + if remote_addr.port() != 53 { + debug!(%remote_addr, "dropping non-DNS UDP datagram for tor backend"); + continue; + } + let response = build_tor_dns_response(&payload, tor_client.as_ref()).await?; + reply_tx + .send(UdpReply { + payload: response, + source: remote_addr, + destination: local_addr, + }) + .await + .context("failed to enqueue tor dns response")?; + } + } + } + None => break, + } + } + } + + session_tasks.abort_all(); + while let Some(result) = session_tasks.join_next().await { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => debug!(?err, "udp session task exited during shutdown"), + Err(err) if err.is_cancelled() => {} + Err(err) => debug!(?err, "udp session task panicked during shutdown"), + } + } + Ok(()) +} + +async fn dispatch_direct_udp( + payload: Vec, + local_addr: SocketAddr, + remote_addr: SocketAddr, + reply_tx: mpsc::Sender, + sessions: Arc>>>>, + session_tasks: &mut JoinSet>, +) -> Result<()> { + let key = UdpFlowKey { + local: local_addr, + remote: remote_addr, + }; + let existing = { sessions.lock().await.get(&key).cloned() }; + if let Some(sender) = existing { + if sender.send(payload.clone()).await.is_ok() { + return Ok(()); + } + sessions.lock().await.remove(&key); + } + + let (tx, rx) = mpsc::channel::>(32); + tx.send(payload) + .await + .context("failed to enqueue outbound udp payload")?; + sessions.lock().await.insert(key.clone(), tx); + + session_tasks.spawn(async move { run_direct_udp_session(key, rx, reply_tx, sessions).await }); + Ok(()) +} + +async fn run_direct_udp_session( + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + sessions: Arc>>>>, +) -> Result<()> { + let bind_addr = match key.remote { + SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), + SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), + }; + let socket = UdpSocket::bind(bind_addr) + .await + .with_context(|| format!("failed to bind udp socket for {}", key.remote))?; + socket + .connect(key.remote) + .await + .with_context(|| format!("failed to connect udp socket to {}", key.remote))?; + + let mut buf = vec![0u8; 65_535]; + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + socket + .send(&payload) + .await + .with_context(|| format!("failed to send udp payload to {}", key.remote))?; + } + None => break, + }, + recv = tokio::time::timeout(UDP_IDLE_TIMEOUT, socket.recv(&mut buf)) => match recv { + Ok(Ok(len)) => { + reply_tx + .send(UdpReply { + payload: buf[..len].to_vec(), + source: key.remote, + destination: key.local, + }) + .await + .context("failed to enqueue inbound udp reply")?; + } + Ok(Err(err)) => return Err(err).with_context(|| format!("failed to receive udp response from {}", key.remote)), + Err(_) => break, + } + } + } + + sessions.lock().await.remove(&key); + Ok(()) +} + +fn wireguard_tun_config(config: &WireGuardConfig) -> Result { + parse_tun_config( + &config.interface.address, + config.interface.mtu, + Some("burrow-wireguard"), + ) +} + +fn socket_tun_config( + addresses: &[String], + mtu: Option, + tun_name: Option<&str>, + default_name: &str, +) -> Result { + let default_addresses; + let addresses = if addresses.is_empty() { + default_addresses = vec![DEFAULT_TUN_V4.to_string(), DEFAULT_TUN_V6.to_string()]; + default_addresses.as_slice() + } else { + addresses + }; + parse_tun_config(addresses, mtu, Some(tun_name.unwrap_or(default_name))) +} + +fn parse_tun_config( + addresses: &[String], + mtu: Option, + tun_name: Option<&str>, +) -> Result { + let addresses = addresses + .iter() + .map(|addr| { + addr.parse::() + .with_context(|| format!("invalid tunnel address '{addr}'")) + }) + .collect::>>()?; + + Ok(TunNetworkConfig { + tun_name: tun_name.unwrap_or("burrow-exec").to_string(), + addresses, + mtu: mtu.unwrap_or(DEFAULT_MTU), + }) +} + +fn open_tun_device(config: &TunNetworkConfig) -> Result { + let tun = TunOptions::new() + .name(&config.tun_name) + .no_pi(true) + .tun_excl(true) + .open() + .context("failed to create tun device")?; + Ok(tun.inner.into_inner()) +} + +fn tokio_tun_from_fd(fd: RawFd) -> Result { + let tun = unsafe { tun::TunInterface::from_raw_fd(fd) }; + TokioTunInterface::new(tun).context("failed to wrap tun fd in tokio interface") +} + +fn read_inner_tun_config() -> Result { + let raw = env::var(INNER_TUN_CONFIG_ENV).context("missing namespace tun config")?; + serde_json::from_str(&raw).context("invalid namespace tun config") +} + +fn configure_tun_addresses( + iface: &tun::TunInterface, + networks: &[IpNetwork], + mtu: u32, +) -> Result<()> { + for network in networks { + match network { + IpNetwork::V4(net) => { + iface.set_ipv4_addr(net.ip())?; + let netmask = prefix_to_netmask_v4(net.prefix()); + iface.set_netmask(netmask)?; + iface.set_broadcast_addr(broadcast_v4(net.ip(), netmask))?; + } + IpNetwork::V6(net) => iface.add_ipv6_addr(net.ip(), net.prefix())?, + } + } + iface.set_mtu(mtu as i32)?; + Ok(()) +} + +fn install_default_routes(name: &str, networks: &[IpNetwork]) -> Result<()> { + if networks + .iter() + .any(|network| matches!(network, IpNetwork::V4(_))) + { + run_ip(["route", "replace", "default", "dev", name])?; + } + if networks + .iter() + .any(|network| matches!(network, IpNetwork::V6(_))) + { + run_ip(["-6", "route", "replace", "default", "dev", name])?; + } + Ok(()) +} + +fn run_ip(args: [&str; N]) -> Result<()> { + let status = StdCommand::new("ip") + .args(args) + .status() + .context("failed to execute ip command")?; + if !status.success() { + bail!("ip {} failed with status {}", args.join(" "), status); + } + Ok(()) +} + +fn set_inheritable(fd: RawFd) -> Result<()> { + let flags = FdFlag::from_bits_truncate( + fcntl(fd, FcntlArg::F_GETFD).context("failed to query descriptor flags")?, + ); + let flags = flags & !FdFlag::FD_CLOEXEC; + fcntl(fd, FcntlArg::F_SETFD(flags)).context("failed to clear close-on-exec")?; + Ok(()) +} + +async fn await_parent_ready(control_fd: RawFd) -> Result<()> { + tokio::task::spawn_blocking(move || -> Result<()> { + let mut control = unsafe { StdUnixStream::from_raw_fd(control_fd) }; + let mut ack = [0u8; 1]; + std::io::Read::read_exact(&mut control, &mut ack) + .context("failed to read namespace ready ack")?; + if ack != *READY_ACK { + bail!("unexpected namespace ready ack"); + } + Ok(()) + }) + .await + .context("failed to join namespace ready wait task")??; + Ok(()) +} + +fn send_tun_fd(control_fd: RawFd, tun_fd: RawFd) -> Result<()> { + let buf = [0u8; 1]; + let iov = [std::io::IoSlice::new(&buf)]; + let fds = [tun_fd]; + sendmsg::<()>( + control_fd, + &iov, + &[ControlMessage::ScmRights(&fds)], + MsgFlags::empty(), + None, + ) + .context("failed to send tun fd to parent")?; + Ok(()) +} + +fn recv_tun_fd(control: &StdUnixStream) -> Result { + let mut buf = [0u8; 1]; + let mut iov = [std::io::IoSliceMut::new(&mut buf)]; + let mut cmsgspace = cmsg_space!([RawFd; 1]); + let msg = recvmsg::<()>( + control.as_raw_fd(), + &mut iov, + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .context("failed to receive tun fd from namespace child")?; + for cmsg in msg.cmsgs() { + if let ControlMessageOwned::ScmRights(fds) = cmsg { + if let Some(fd) = fds.first() { + return Ok(*fd); + } + } + } + bail!("namespace child did not send a tun fd") +} + +fn ensure_tool(tool: &str) -> Result<()> { + let status = StdCommand::new("sh") + .args(["-lc", &format!("command -v {tool} >/dev/null")]) + .status() + .with_context(|| format!("failed to probe required tool '{tool}'"))?; + if !status.success() { + bail!("required host tool '{tool}' is not available"); + } + Ok(()) +} + +async fn read_optional_payload(path: Option<&Path>) -> Result> { + match path { + Some(path) => tokio::fs::read(path) + .await + .with_context(|| format!("failed to read payload from {}", path.display())), + None => Ok(Vec::new()), + } +} + +async fn read_required_payload(path: Option<&Path>, backend: &str) -> Result> { + let path = path.ok_or_else(|| anyhow!("{backend} exec requires --payload"))?; + tokio::fs::read(path) + .await + .with_context(|| format!("failed to read payload from {}", path.display())) +} + +fn parse_wireguard_payload(payload: &[u8], path: Option<&Path>) -> Result { + let payload = str::from_utf8(payload).context("wireguard payload must be valid UTF-8")?; + if let Some(path) = path { + if let Some(ext) = path.extension().and_then(|ext| ext.to_str()) { + return WireGuardConfig::from_content_fmt(payload, ext); + } + } + + WireGuardConfig::from_toml(payload).or_else(|_| WireGuardConfig::from_ini(payload)) +} + +async fn spawn_child(command: &[String]) -> Result { + let mut cmd = Command::new(&command[0]); + if command.len() > 1 { + cmd.args(&command[1..]); + } + cmd.stdin(std::process::Stdio::inherit()); + cmd.stdout(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); + cmd.kill_on_drop(true); + cmd.status() + .await + .with_context(|| format!("failed to spawn '{}'", command[0])) +} + +fn child_exit_code(status: ExitStatus) -> Result { + if let Some(code) = status.code() { + return Ok(code); + } + if let Some(signal) = status.signal() { + return Ok(128 + signal); + } + bail!("child process terminated without an exit code"); +} + +fn prefix_to_netmask_v4(prefix: u8) -> Ipv4Addr { + if prefix == 0 { + Ipv4Addr::new(0, 0, 0, 0) + } else { + let mask = (!0u32) << (32 - prefix); + Ipv4Addr::from(mask) + } +} + +fn broadcast_v4(ip: Ipv4Addr, netmask: Ipv4Addr) -> Ipv4Addr { + let ip_u32 = u32::from(ip); + let mask = u32::from(netmask); + Ipv4Addr::from(ip_u32 | !mask) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_direct_json_payload() { + let payload = br#"{"address":["10.0.0.2/24"],"mtu":1400,"tun_name":"burrow0"}"#; + let config = DirectConfig::from_payload(payload).unwrap(); + assert_eq!(config.address, vec!["10.0.0.2/24"]); + assert_eq!(config.mtu, Some(1400)); + assert_eq!(config.tun_name.as_deref(), Some("burrow0")); + } + + #[test] + fn socket_tun_config_uses_dual_stack_defaults() { + let config = socket_tun_config(&[], None, None, "burrow-test").unwrap(); + assert_eq!(config.tun_name, "burrow-test"); + assert!(config + .addresses + .iter() + .any(|network| matches!(network, IpNetwork::V4(_)))); + assert!(config + .addresses + .iter() + .any(|network| matches!(network, IpNetwork::V6(_)))); + } +} diff --git a/burrow/src/wireguard/iface.rs b/burrow/src/wireguard/iface.rs index 321801b..5b61861 100755 --- a/burrow/src/wireguard/iface.rs +++ b/burrow/src/wireguard/iface.rs @@ -148,7 +148,7 @@ impl Interface { debug!("Routing packet to {}", dst_addr); let Some(idx) = pcbs.find(dst_addr) else { - continue + continue; }; debug!("Found peer:{}", idx); diff --git a/burrow/src/wireguard/noise/handshake.rs b/burrow/src/wireguard/noise/handshake.rs index 2ec0c6a..65136bc 100755 --- a/burrow/src/wireguard/noise/handshake.rs +++ b/burrow/src/wireguard/noise/handshake.rs @@ -9,20 +9,15 @@ use std::{ use aead::{Aead, Payload}; use blake2::{ digest::{FixedOutput, KeyInit}, - Blake2s256, - Blake2sMac, - Digest, + Blake2s256, Blake2sMac, Digest, }; use chacha20poly1305::XChaCha20Poly1305; use rand_core::OsRng; use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305}; +use subtle::ConstantTimeEq; use super::{ - errors::WireGuardError, - session::Session, - x25519, - HandshakeInit, - HandshakeResponse, + errors::WireGuardError, session::Session, x25519, HandshakeInit, HandshakeResponse, PacketCookieReply, }; @@ -209,7 +204,7 @@ impl Tai64N { /// Parse a timestamp from a 12 byte u8 slice fn parse(buf: &[u8; 12]) -> Result { if buf.len() < 12 { - return Err(WireGuardError::InvalidTai64nTimestamp) + return Err(WireGuardError::InvalidTai64nTimestamp); } let (sec_bytes, nano_bytes) = buf.split_at(std::mem::size_of::()); @@ -534,11 +529,14 @@ impl Handshake { &hash, )?; - ring::constant_time::verify_slices_are_equal( - self.params.peer_static_public.as_bytes(), - &peer_static_public_decrypted, - ) - .map_err(|_| WireGuardError::WrongKey)?; + if !bool::from( + self.params + .peer_static_public + .as_bytes() + .ct_eq(&peer_static_public_decrypted), + ) { + return Err(WireGuardError::WrongKey); + } // initiator.hash = HASH(initiator.hash || msg.encrypted_static) hash = b2s_hash(&hash, packet.encrypted_static); @@ -556,19 +554,22 @@ impl Handshake { let timestamp = Tai64N::parse(×tamp)?; if !timestamp.after(&self.last_handshake_timestamp) { // Possibly a replay - return Err(WireGuardError::WrongTai64nTimestamp) + return Err(WireGuardError::WrongTai64nTimestamp); } self.last_handshake_timestamp = timestamp; // initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp) hash = b2s_hash(&hash, packet.encrypted_timestamp); - self.previous = std::mem::replace(&mut self.state, HandshakeState::InitReceived { - chaining_key, - hash, - peer_ephemeral_public, - peer_index, - }); + self.previous = std::mem::replace( + &mut self.state, + HandshakeState::InitReceived { + chaining_key, + hash, + peer_ephemeral_public, + peer_index, + }, + ); self.format_handshake_response(dst) } @@ -669,7 +670,7 @@ impl Handshake { let local_index = self.cookies.index; if packet.receiver_idx != local_index { - return Err(WireGuardError::WrongIndex) + return Err(WireGuardError::WrongIndex); } // msg.encrypted_cookie = XAEAD(HASH(LABEL_COOKIE || responder.static_public), // msg.nonce, cookie, last_received_msg.mac1) @@ -725,7 +726,7 @@ impl Handshake { dst: &'a mut [u8], ) -> Result<&'a mut [u8], WireGuardError> { if dst.len() < super::HANDSHAKE_INIT_SZ { - return Err(WireGuardError::DestinationBufferTooSmall) + return Err(WireGuardError::DestinationBufferTooSmall); } let (message_type, rest) = dst.split_at_mut(4); @@ -808,7 +809,7 @@ impl Handshake { dst: &'a mut [u8], ) -> Result<(&'a mut [u8], Session), WireGuardError> { if dst.len() < super::HANDSHAKE_RESP_SZ { - return Err(WireGuardError::DestinationBufferTooSmall) + return Err(WireGuardError::DestinationBufferTooSmall); } let state = std::mem::replace(&mut self.state, HandshakeState::None); diff --git a/burrow/src/wireguard/noise/mod.rs b/burrow/src/wireguard/noise/mod.rs index aa06652..86bcc73 100755 --- a/burrow/src/wireguard/noise/mod.rs +++ b/burrow/src/wireguard/noise/mod.rs @@ -133,9 +133,9 @@ pub enum Packet<'a> { impl Tunnel { #[inline(always)] - pub fn parse_incoming_packet(src: &[u8]) -> Result { + pub fn parse_incoming_packet(src: &[u8]) -> Result, WireGuardError> { if src.len() < 4 { - return Err(WireGuardError::InvalidPacket) + return Err(WireGuardError::InvalidPacket); } // Checks the type, as well as the reserved zero fields @@ -177,7 +177,7 @@ impl Tunnel { pub fn dst_address(packet: &[u8]) -> Option { if packet.is_empty() { - return None + return None; } match packet[0] >> 4 { @@ -201,7 +201,7 @@ impl Tunnel { pub fn src_address(packet: &[u8]) -> Option { if packet.is_empty() { - return None + return None; } match packet[0] >> 4 { @@ -296,7 +296,7 @@ impl Tunnel { self.timer_tick(TimerName::TimeLastDataPacketSent); } self.tx_bytes += src.len(); - return TunnResult::WriteToNetwork(packet) + return TunnResult::WriteToNetwork(packet); } // If there is no session, queue the packet for future retry @@ -320,7 +320,7 @@ impl Tunnel { ) -> TunnResult<'a> { if datagram.is_empty() { // Indicates a repeated call - return self.send_queued_packet(dst) + return self.send_queued_packet(dst); } let mut cookie = [0u8; COOKIE_REPLY_SZ]; @@ -331,7 +331,7 @@ impl Tunnel { Ok(packet) => packet, Err(TunnResult::WriteToNetwork(cookie)) => { dst[..cookie.len()].copy_from_slice(cookie); - return TunnResult::WriteToNetwork(&mut dst[..cookie.len()]) + return TunnResult::WriteToNetwork(&mut dst[..cookie.len()]); } Err(TunnResult::Err(e)) => return TunnResult::Err(e), _ => unreachable!(), @@ -435,7 +435,7 @@ impl Tunnel { let cur_idx = self.current; if cur_idx == new_idx { // There is nothing to do, already using this session, this is the common case - return + return; } if self.sessions[cur_idx % N_SESSIONS].is_none() || self.timers.session_timers[new_idx % N_SESSIONS] @@ -481,7 +481,7 @@ impl Tunnel { force_resend: bool, ) -> TunnResult<'a> { if self.handshake.is_in_progress() && !force_resend { - return TunnResult::Done + return TunnResult::Done; } if self.handshake.is_expired() { @@ -540,7 +540,7 @@ impl Tunnel { }; if computed_len > packet.len() { - return TunnResult::Err(WireGuardError::InvalidPacket) + return TunnResult::Err(WireGuardError::InvalidPacket); } self.timer_tick(TimerName::TimeLastDataPacketReceived); diff --git a/burrow/src/wireguard/noise/rate_limiter.rs b/burrow/src/wireguard/noise/rate_limiter.rs index ff19efd..e4fde02 100755 --- a/burrow/src/wireguard/noise/rate_limiter.rs +++ b/burrow/src/wireguard/noise/rate_limiter.rs @@ -8,23 +8,13 @@ use aead::{generic_array::GenericArray, AeadInPlace, KeyInit}; use chacha20poly1305::{Key, XChaCha20Poly1305}; use parking_lot::Mutex; use rand_core::{OsRng, RngCore}; -use ring::constant_time::verify_slices_are_equal; +use subtle::ConstantTimeEq; use super::{ handshake::{ - b2s_hash, - b2s_keyed_mac_16, - b2s_keyed_mac_16_2, - b2s_mac_24, - LABEL_COOKIE, - LABEL_MAC1, + b2s_hash, b2s_keyed_mac_16, b2s_keyed_mac_16_2, b2s_mac_24, LABEL_COOKIE, LABEL_MAC1, }, - HandshakeInit, - HandshakeResponse, - Packet, - TunnResult, - Tunnel, - WireGuardError, + HandshakeInit, HandshakeResponse, Packet, TunnResult, Tunnel, WireGuardError, }; const COOKIE_REFRESH: u64 = 128; // Use 128 and not 120 so the compiler can optimize out the division @@ -136,7 +126,7 @@ impl RateLimiter { dst: &'a mut [u8], ) -> Result<&'a mut [u8], WireGuardError> { if dst.len() < super::COOKIE_REPLY_SZ { - return Err(WireGuardError::DestinationBufferTooSmall) + return Err(WireGuardError::DestinationBufferTooSmall); } let (message_type, rest) = dst.split_at_mut(4); @@ -185,8 +175,9 @@ impl RateLimiter { let (mac1, mac2) = macs.split_at(16); let computed_mac1 = b2s_keyed_mac_16(&self.mac1_key, msg); - verify_slices_are_equal(&computed_mac1[..16], mac1) - .map_err(|_| TunnResult::Err(WireGuardError::InvalidMac))?; + if !bool::from(computed_mac1[..16].ct_eq(mac1)) { + return Err(TunnResult::Err(WireGuardError::InvalidMac)); + } if self.is_under_load() { let addr = match src_addr { @@ -198,11 +189,11 @@ impl RateLimiter { let cookie = self.current_cookie(addr); let computed_mac2 = b2s_keyed_mac_16_2(&cookie, msg, mac1); - if verify_slices_are_equal(&computed_mac2[..16], mac2).is_err() { + if !bool::from(computed_mac2[..16].ct_eq(mac2)) { let cookie_packet = self .format_cookie_reply(sender_idx, cookie, mac1, dst) .map_err(TunnResult::Err)?; - return Err(TunnResult::WriteToNetwork(cookie_packet)) + return Err(TunnResult::WriteToNetwork(cookie_packet)); } } } diff --git a/burrow/src/wireguard/noise/session.rs b/burrow/src/wireguard/noise/session.rs index 8988728..14c191b 100755 --- a/burrow/src/wireguard/noise/session.rs +++ b/burrow/src/wireguard/noise/session.rs @@ -88,11 +88,11 @@ impl ReceivingKeyCounterValidator { fn will_accept(&self, counter: u64) -> Result<(), WireGuardError> { if counter >= self.next { // As long as the counter is growing no replay took place for sure - return Ok(()) + return Ok(()); } if counter + N_BITS < self.next { // Drop if too far back - return Err(WireGuardError::InvalidCounter) + return Err(WireGuardError::InvalidCounter); } if !self.check_bit(counter) { Ok(()) @@ -107,22 +107,22 @@ impl ReceivingKeyCounterValidator { fn mark_did_receive(&mut self, counter: u64) -> Result<(), WireGuardError> { if counter + N_BITS < self.next { // Drop if too far back - return Err(WireGuardError::InvalidCounter) + return Err(WireGuardError::InvalidCounter); } if counter == self.next { // Usually the packets arrive in order, in that case we simply mark the bit and // increment the counter self.set_bit(counter); self.next += 1; - return Ok(()) + return Ok(()); } if counter < self.next { // A packet arrived out of order, check if it is valid, and mark if self.check_bit(counter) { - return Err(WireGuardError::InvalidCounter) + return Err(WireGuardError::InvalidCounter); } self.set_bit(counter); - return Ok(()) + return Ok(()); } // Packets where dropped, or maybe reordered, skip them and mark unused if counter - self.next >= N_BITS { @@ -247,7 +247,7 @@ impl Session { panic!("The destination buffer is too small"); } if packet.receiver_idx != self.receiving_index { - return Err(WireGuardError::WrongIndex) + return Err(WireGuardError::WrongIndex); } // Don't reuse counters, in case this is a replay attack we want to quickly // check the counter without running expensive decryption diff --git a/burrow/src/wireguard/noise/timers.rs b/burrow/src/wireguard/noise/timers.rs index 1d0cf1f..f713e6f 100755 --- a/burrow/src/wireguard/noise/timers.rs +++ b/burrow/src/wireguard/noise/timers.rs @@ -190,7 +190,7 @@ impl Tunnel { { if self.handshake.is_expired() { - return TunnResult::Err(WireGuardError::ConnectionExpired) + return TunnResult::Err(WireGuardError::ConnectionExpired); } // Clear cookie after COOKIE_EXPIRATION_TIME @@ -206,7 +206,7 @@ impl Tunnel { tracing::error!("CONNECTION_EXPIRED(REJECT_AFTER_TIME * 3)"); self.handshake.set_expired(); self.clear_all(); - return TunnResult::Err(WireGuardError::ConnectionExpired) + return TunnResult::Err(WireGuardError::ConnectionExpired); } if let Some(time_init_sent) = self.handshake.timer() { @@ -219,7 +219,7 @@ impl Tunnel { tracing::error!("CONNECTION_EXPIRED(REKEY_ATTEMPT_TIME)"); self.handshake.set_expired(); self.clear_all(); - return TunnResult::Err(WireGuardError::ConnectionExpired) + return TunnResult::Err(WireGuardError::ConnectionExpired); } if time_init_sent.elapsed() >= REKEY_TIMEOUT { @@ -299,11 +299,11 @@ impl Tunnel { } if handshake_initiation_required { - return self.format_handshake_initiation(dst, true) + return self.format_handshake_initiation(dst, true); } if keepalive_required { - return self.encapsulate(&[], dst) + return self.encapsulate(&[], dst); } TunnResult::Done diff --git a/burrow/src/wireguard/pcb.rs b/burrow/src/wireguard/pcb.rs index 974d84e..6e5e6c0 100755 --- a/burrow/src/wireguard/pcb.rs +++ b/burrow/src/wireguard/pcb.rs @@ -64,7 +64,7 @@ impl PeerPcb { let guard = self.socket.read().await; let Some(socket) = guard.as_ref() else { self.open_if_closed().await?; - continue + continue; }; let mut res_buf = [0; 1500]; // tracing::debug!("{} : waiting for readability on {:?}", rid, socket); @@ -72,7 +72,7 @@ impl PeerPcb { Ok(l) => l, Err(e) => { log::error!("{}: error reading from socket: {:?}", rid, e); - continue + continue; } }; let mut res_dat = &res_buf[..len]; @@ -88,7 +88,7 @@ impl PeerPcb { TunnResult::Done => break, TunnResult::Err(e) => { tracing::error!(message = "Decapsulate error", error = ?e); - break + break; } TunnResult::WriteToNetwork(packet) => { tracing::debug!("WriteToNetwork: {:?}", packet); @@ -102,17 +102,29 @@ impl PeerPcb { .await?; tracing::debug!("WriteToNetwork done"); res_dat = &[]; - continue + continue; } TunnResult::WriteToTunnelV4(packet, addr) => { tracing::debug!("WriteToTunnelV4: {:?}, {:?}", packet, addr); - tun_interface.read().await.as_ref().ok_or(anyhow::anyhow!("tun interface does not exist"))?.send(packet).await?; - break + tun_interface + .read() + .await + .as_ref() + .ok_or(anyhow::anyhow!("tun interface does not exist"))? + .send(packet) + .await?; + break; } TunnResult::WriteToTunnelV6(packet, addr) => { tracing::debug!("WriteToTunnelV6: {:?}, {:?}", packet, addr); - tun_interface.read().await.as_ref().ok_or(anyhow::anyhow!("tun interface does not exist"))?.send(packet).await?; - break + tun_interface + .read() + .await + .as_ref() + .ok_or(anyhow::anyhow!("tun interface does not exist"))? + .send(packet) + .await?; + break; } } } @@ -134,7 +146,7 @@ impl PeerPcb { let handle = self.socket.read().await; let Some(socket) = handle.as_ref() else { tracing::error!("No socket for peer"); - return Ok(()) + return Ok(()); }; tracing::debug!("Our Encapsulated packet: {:?}", packet); socket.send(packet).await?; @@ -157,7 +169,7 @@ impl PeerPcb { let handle = self.socket.read().await; let Some(socket) = handle.as_ref() else { tracing::error!("No socket for peer"); - return Ok(()) + return Ok(()); }; socket.send(packet).await?; tracing::debug!("Sent Packet for timer update"); diff --git a/docs/FORWARDEMAIL.md b/docs/FORWARDEMAIL.md new file mode 100644 index 0000000..798f3e5 --- /dev/null +++ b/docs/FORWARDEMAIL.md @@ -0,0 +1,101 @@ +# Forward Email Backups + +Burrow's mail direction is hosted mail on [Forward Email](https://forwardemail.net/), with domain-owned backup retention in our own S3-compatible object storage. + +This is the first mail path to operationalize for `burrow.net` and `burrow.rs`. It keeps SMTP/IMAP hosting off the first forge host while still giving Burrow control over backup retention and object ownership. + +## What Forward Email Requires + +Forward Email exposes custom backup storage per domain. The documented API shape is: + +- `PUT /v1/domains/{domain}` with: + - `has_custom_s3=true` + - `s3_endpoint` + - `s3_access_key_id` + - `s3_secret_access_key` + - `s3_region` + - `s3_bucket` +- `POST /v1/domains/{domain}/test-s3-connection` + +Forward Email also documents these operational constraints: + +- the bucket must remain private +- credentials are validated with `HeadBucket` +- failed or public-bucket configurations fall back to Forward Email's default storage and notify domain administrators +- custom S3 keeps every backup version, so lifecycle expiration is our responsibility + +## Burrow Secret Layout + +Present in `intake/` today: + +- `intake/forwardemail_api_token.txt` +- `intake/hetzner-s3-user.txt` +- `intake/hetzner-s3-secret.txt` +- Hetzner public S3 endpoint for Forward Email: `https://hel1.your-objectstorage.com` +- Hetzner object storage region: `hel1` +- Hetzner bucket used for Forward Email backups: `burrow` + +## Verified Storage State + +As of March 15, 2026, Burrow's Forward Email custom S3 configuration is live: + +- endpoint: `https://hel1.your-objectstorage.com` +- region: `hel1` +- bucket: `burrow` +- `burrow.net` has `has_custom_s3=true` +- `burrow.rs` has `has_custom_s3=true` +- Forward Email's `/test-s3-connection` succeeded for both domains +- the `burrow` bucket enforces lifecycle expiration after `90` days + +Forward Email performs bucket validation with bucket-style addressing. For Hetzner Object Storage, this means the working endpoint is the regional S3 endpoint (`https://hel1.your-objectstorage.com`), not the account alias (`https://burrow.hel1.your-objectstorage.com`). Using the account alias causes TLS hostname mismatches when the vendor prepends the bucket name. + +## Helper + +Use [`Tools/forwardemail-custom-s3.sh`](../Tools/forwardemail-custom-s3.sh) to configure or retest the domain setting without putting secrets on the process list. + +Use [`Tools/forwardemail-hetzner-storage.py`](../Tools/forwardemail-hetzner-storage.py) to ensure the Hetzner backup bucket exists and to apply lifecycle expiry before enabling custom S3 on the Forward Email side. + +Bucket bootstrap example: + +```sh +Tools/forwardemail-hetzner-storage.py \ + --endpoint https://hel1.your-objectstorage.com \ + --bucket burrow \ + --expire-days 90 +``` + +Example: + +```sh +Tools/forwardemail-custom-s3.sh \ + --domain burrow.net \ + --api-token-file intake/forwardemail_api_token.txt \ + --s3-endpoint https://hel1.your-objectstorage.com \ + --s3-region hel1 \ + --s3-bucket burrow \ + --s3-access-key-file intake/hetzner-s3-user.txt \ + --s3-secret-key-file intake/hetzner-s3-secret.txt +``` + +Retest an existing domain configuration without rewriting it: + +```sh +Tools/forwardemail-custom-s3.sh \ + --domain burrow.net \ + --api-token-file intake/forwardemail_api_token.txt \ + --test-only +``` + +## Retention + +Forward Email preserves every backup object when custom S3 is enabled. Configure lifecycle expiration on the bucket itself. A 30-day or 90-day expiry window is the baseline recommendation from the vendor docs; Burrow should choose explicitly per domain instead of letting the bucket grow without bound. The current Burrow bootstrap helper defaults to `90` days. + +## Identity Direction + +Hosted mail and SaaS identity are separate concerns: + +- mail hosting/backups: Forward Email + Burrow-owned S3-compatible storage +- interactive identity: Authentik as the long-term IdP +- future SaaS SSO target: Linear via SAML once the workspace and plan are ready + +This means the forge host does not need to become the first mail server just to give Burrow mailboxes or retention control. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 764c219..346f7e7 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -98,10 +98,14 @@ code burrow You can run burrow on the command line with cargo: ``` -cargo run +sudo -E cargo run -- start ``` -Cargo will ask for your password because burrow needs permission in order to create a tunnel. +Creating the tunnel requires elevated privileges. Regular checks and tests can run without `sudo`: + +``` +cargo test --workspace --all-features +``` diff --git a/docs/PROTOCOL_ROADMAP.md b/docs/PROTOCOL_ROADMAP.md new file mode 100644 index 0000000..37c7228 --- /dev/null +++ b/docs/PROTOCOL_ROADMAP.md @@ -0,0 +1,31 @@ +# Protocol Roadmap + +Burrow currently has two tunnel paths in-tree: + +- a WireGuard data plane +- a Tor-backed userspace TCP path + +What it does not have yet is a transport-neutral control plane that can honestly claim full MASQUE `CONNECT-IP` or full Tailscale-style negotiation parity. This repository now contains the beginnings of that layer: + +- control-plane data structures in `burrow/src/control/mod.rs` +- local auth bootstrap and persistent node/session storage in `burrow/src/auth/server/` +- governance documents under `evolution/` for the bigger protocol work + +## `CONNECT-IP` + +Full RFC 9484 support requires more than packet forwarding. It needs HTTP/3 session management, Capsule handling, HTTP Datagram context identifiers, address assignment, route advertisement, and request-scope enforcement. Burrow does not implement those end to end yet. + +## Tailscale-Style Negotiation + +Burrow now has register/map request and response types plus persistent node records, but it does not yet implement the full Tailscale capability surface, peer delta protocol, DERP coordination, or Noise-based control transport. + +## Current Direction + +The intended sequence is: + +1. Stabilize the control-plane data model and bootstrap auth. +2. Introduce transport-neutral route and address abstractions. +3. Add MASQUE framing and HTTP/3 transport support. +4. Expand policy, relay, and interoperability testing. + +This keeps Burrow honest about what is running today while creating a clean path for the rest. diff --git a/docs/WIREGUARD_LINEAGE.md b/docs/WIREGUARD_LINEAGE.md new file mode 100644 index 0000000..63e8839 --- /dev/null +++ b/docs/WIREGUARD_LINEAGE.md @@ -0,0 +1,30 @@ +# WireGuard Rust Lineage + +Burrow's in-tree WireGuard engine is not a greenfield implementation. It was lifted from the Rust WireGuard lineage around Cloudflare's BoringTun, then cut down and reshaped to fit Burrow's own daemon and tunnel abstractions. + +## What Was Lifted + +- The repository history includes `1b39eca` (`boringtun wip`) and `28af9003` (`merge boringtun into burrow`). +- The current `burrow/src/wireguard/noise/*` files still carry the original Cloudflare copyright and SPDX headers. +- Core protocol machinery such as the Noise handshake, session state, rate limiter, and timer logic came from that imported body of work. + +## What Changed in Burrow + +Burrow does not embed BoringTun unchanged. + +- The original device layer was replaced with Burrow-specific interface and peer control blocks in `burrow/src/wireguard/iface.rs` and `burrow/src/wireguard/pcb.rs`. +- Configuration handling was rewritten around Burrow's own INI parser and config model in `burrow/src/wireguard/config.rs`. +- The daemon now resolves the active runtime from the database-backed network list rather than from a single static WireGuard payload. +- Burrow added its own runtime switching path so WireGuard can share one daemon lifecycle with the rest of the managed runtime system. + +## What Was Improved + +The lifted code has been tightened further in-repo. + +- Deprecated constant-time comparisons were replaced with `subtle`. +- Network ordering and runtime selection are now deterministic and test-covered. +- The Burrow runtime can swap between WireGuard configurations without restarting the daemon process itself. + +## Why This Matters + +This project should be explicit about lineage. Burrow benefits from proven Rust WireGuard work, but it owns the integration surface, runtime behavior, and future maintenance burden. That is why the code should be documented as lifted, modified, and improved rather than described as wholly original. diff --git a/evolution/README.md b/evolution/README.md new file mode 100644 index 0000000..e55a347 --- /dev/null +++ b/evolution/README.md @@ -0,0 +1,60 @@ +# Burrow Evolution + +Burrow Evolution Proposals (BEPs) are the repository's durable design record for protocol work, control-plane changes, forge infrastructure, and operational policy. + +## Goals + +1. Capture intent before implementation outruns the architecture. +2. Give contributors and agents enough context to work safely without re-discovering prior decisions. +3. Tie ambitious work to concrete validation, rollout, and rollback criteria. + +## When a BEP is required + +Open a BEP for: + +- new transports or protocol families +- control-plane and identity changes +- deployment, forge, runner, or secrets changes +- data model migrations +- user-visible behavior that changes security or routing semantics + +Small bug fixes and isolated refactors do not need a BEP unless they materially change one of the areas above. + +## Lifecycle + +1. Pitch + Capture the problem and why it matters now. +2. Draft + Copy `evolution/proposals/0000-template.md` to `evolution/proposals/BEP-XXXX-short-slug.md`. +3. Review + Collect feedback, tighten the design, and document unresolved concerns. +4. Decision + Mark the proposal `Accepted`, `Rejected`, or `Returned for Revision`. +5. Implementation + Link code changes, tests, and rollout evidence. +6. Supersession + Keep historical proposals in-tree and point forward to the replacing BEP. + +## Status Values + +- `Pitch` +- `Draft` +- `In Review` +- `Accepted` +- `Implemented` +- `Rejected` +- `Returned for Revision` +- `Superseded` +- `Archived` + +## Layout + +```text +evolution/ + README.md + proposals/ + 0000-template.md + BEP-0001-... +``` + +Use ASCII Markdown. Keep metadata at the top of each proposal so tooling and future agents can parse it quickly. diff --git a/evolution/proposals/0000-template.md b/evolution/proposals/0000-template.md new file mode 100644 index 0000000..66954c6 --- /dev/null +++ b/evolution/proposals/0000-template.md @@ -0,0 +1,57 @@ +# `BEP-XXXX` - Title Case Summary + +```text +Status: Draft | In Review | Accepted | Implemented | Rejected | Returned for Revision | Superseded | Archived +Proposal: BEP-XXXX +Authors: +Coordinator: +Reviewers: +Constitution Sections: +Implementation PRs: (optional while drafting) +Decision Date: +``` + +## Summary + +One or two paragraphs that state the desired outcome and why it matters. + +## Motivation + +- What problem exists today? +- Why should Burrow solve it now? +- Which issues, incidents, or constraints support the change? + +## Detailed Design + +- Architecture and boundaries +- Data model and migration plan +- Protocol or API changes +- Observability, testing, and failure handling + +## Security and Operational Considerations + +- Access and secret handling +- Abuse, downgrade, or supply-chain risks +- Rollback and kill-switch plans + +## Contributor Playbook + +Give the concrete steps, commands, checks, and evidence a contributor should produce while implementing or rolling out the change. + +## Alternatives Considered + +List alternatives and why they were rejected. + +## Impact on Other Work + +- follow-up tasks +- dependencies +- compatibility constraints + +## Decision + +Record the final call, who made it, and any conditions. + +## References + +Link relevant issues, specs, transcripts, and external research. diff --git a/evolution/proposals/BEP-0001-sovereign-forge-and-governance.md b/evolution/proposals/BEP-0001-sovereign-forge-and-governance.md new file mode 100644 index 0000000..f48a7a9 --- /dev/null +++ b/evolution/proposals/BEP-0001-sovereign-forge-and-governance.md @@ -0,0 +1,61 @@ +# `BEP-0001` - Sovereign Forge and Governance Bootstrap + +```text +Status: Draft +Proposal: BEP-0001 +Authors: gpt-5.4 +Coordinator: gpt-5.4 +Reviewers: Pending +Constitution Sections: II, III, V +Implementation PRs: Pending +Decision Date: Pending +``` + +## Summary + +Burrow should own its forge, deployment logic, and operational context under `burrow.net`. This proposal establishes the repository-local governance and forge bootstrap required to move build, release, and infrastructure control out of GitHub-centric assumptions and into a self-hosted operating model. + +## Motivation + +- The repository currently keeps CI definitions under `.github/workflows/` but has no first-class self-hosted forge layout. +- Infrastructure changes and protocol work are already entangled; without a design record, the project risks landing irreversible operations without enough context. +- A self-hosted forge is a prerequisite for durable autonomy over source, runners, and release pipelines. + +## Detailed Design + +- Add a project constitution and BEP process under `evolution/`. +- Introduce a Nix flake and NixOS host/module layout for `burrow-forge`. +- Add Forgejo-native workflows under `.forgejo/workflows/` for repository-local CI. +- Bootstrap the initial forge identity around `contact@burrow.net` and an agent-owned SSH workflow. + +## Security and Operational Considerations + +- Initial bootstrap may read credentials from local intake, but production must converge on encrypted secret handling. +- The first forge host replacement must preserve rollback information before deleting any existing VM. +- DNS for `burrow.net` is currently pending activation; the forge rollout must not assume public reachability until nameserver cutover completes. + +## Contributor Playbook + +- Keep destructive host operations behind explicit verification of the current Hetzner state. +- Build and test repository-local workflows before using them for deployment. +- Record the active server id, image, IPs, and SSH path before replacement. + +## Alternatives Considered + +- Continue relying on GitHub Actions while separately hosting services. Rejected because it leaves source authority and CI policy split across systems. +- Stand up Forgejo without a repository-local operating model. Rejected because the repo would still be missing deployment truth. + +## Impact on Other Work + +- Blocks long-term migration of workflows away from GitHub. +- Provides the governance anchor for protocol and control-plane proposals. + +## Decision + +Pending. + +## References + +- `CONSTITUTION.md` +- `.github/workflows/` +- `.forgejo/workflows/` diff --git a/evolution/proposals/BEP-0002-control-plane-bootstrap-and-local-auth.md b/evolution/proposals/BEP-0002-control-plane-bootstrap-and-local-auth.md new file mode 100644 index 0000000..2558d09 --- /dev/null +++ b/evolution/proposals/BEP-0002-control-plane-bootstrap-and-local-auth.md @@ -0,0 +1,60 @@ +# `BEP-0002` - Control-Plane Bootstrap and Local Auth + +```text +Status: Draft +Proposal: BEP-0002 +Authors: gpt-5.4 +Coordinator: gpt-5.4 +Reviewers: Pending +Constitution Sections: I, II, III, V +Implementation PRs: Pending +Decision Date: Pending +``` + +## Summary + +Burrow needs a repository-owned control-plane model instead of ad hoc network payload storage plus third-party-only auth. This proposal introduces a local username/password bootstrap for `contact@burrow.net`, plus a register/map data model shaped to support a Tailscale-style control server without claiming full parity yet. + +## Motivation + +- Current auth support is limited and does not provide a plain local bootstrap path for the project's own operator identity. +- The existing database stores network payloads, but not a durable model for users, nodes, sessions, or control-plane negotiation state. +- Future work on route policy, device coordination, and richer negotiation needs a real data model now. + +## Detailed Design + +- Add control-plane types for users, nodes, register requests, and map responses. +- Extend the auth server schema with local credentials, sessions, provider logins, and control nodes. +- Expose JSON endpoints for local login, node registration, and map retrieval. +- Seed the initial operator account from intake-backed bootstrap credentials. + +## Security and Operational Considerations + +- Passwords are stored with Argon2id hashes only. +- Session tokens are bearer credentials and must be treated as sensitive. +- The bootstrap credential path is a short-term path; follow-up work should move it into encrypted secret management before public deployment. + +## Contributor Playbook + +- Verify bootstrap account creation in an isolated test database. +- Exercise login, register, and map end to end with integration tests. +- Do not advertise protocol parity beyond the implemented request/response contract. + +## Alternatives Considered + +- Wait for full external identity-provider integration first. Rejected because the forge needs an operator account now. +- Keep control-plane state implicit in daemon-local configuration. Rejected because it cannot express multi-device coordination. + +## Impact on Other Work + +- Unblocks forge bootstrap and future device control-plane work. +- Creates the storage model needed for richer policy and transport proposals. + +## Decision + +Pending. + +## References + +- `burrow/src/auth/server/` +- `burrow/src/control/` diff --git a/evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md b/evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md new file mode 100644 index 0000000..99ddedf --- /dev/null +++ b/evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md @@ -0,0 +1,61 @@ +# `BEP-0003` - CONNECT-IP and Negotiation Roadmap + +```text +Status: Draft +Proposal: BEP-0003 +Authors: gpt-5.4 +Coordinator: gpt-5.4 +Reviewers: Pending +Constitution Sections: I, II, V +Implementation PRs: Pending +Decision Date: Pending +``` + +## Summary + +Burrow should grow from a WireGuard-first tunnel runner into a transport stack that can support HTTP/3 MASQUE `CONNECT-IP` and a richer node negotiation model. This proposal stages that work so Burrow can adopt the right abstractions instead of stapling QUIC-era semantics onto a WireGuard-only daemon. + +## Motivation + +- `CONNECT-IP` introduces HTTP/3 sessions, context identifiers, address assignment, and route advertisements that do not fit the current daemon model. +- A Tailscale-style control plane requires explicit node, endpoint, and session state rather than raw network blobs. +- The project needs a roadmap that distinguishes data-model work, control-plane work, and actual transport implementation. + +## Detailed Design + +- Stage 1: land control-plane types and persistent auth/session/node storage. +- Stage 2: add transport-agnostic route, address-assignment, and policy abstractions in Burrow. +- Stage 3: implement MASQUE `CONNECT-IP` framing and HTTP Datagram handling. +- Stage 4: connect the transport layer to real relay, policy, and observability paths. + +## Security and Operational Considerations + +- `CONNECT-IP` changes the trust boundary from WireGuard peers to HTTP/3 peers and relays; authentication, replay handling, and scope restriction must be explicit. +- Route advertisements and delegated prefixes must be validated before touching the data plane. +- Control-plane capability claims must not imply support that the transport layer does not yet implement. + +## Contributor Playbook + +- Keep protocol codecs independently testable before integrating them into live transports. +- Add interoperability tests for every new capsule or datagram type. +- Separate request parsing, policy validation, and packet forwarding so regressions stay localized. + +## Alternatives Considered + +- Implement MASQUE directly in the daemon without control-plane refactoring. Rejected because the current daemon has no transport-neutral contract for routes or prefixes. +- Treat Tailscale negotiation as a one-off compatibility shim. Rejected because Burrow needs first-class control-plane concepts either way. + +## Impact on Other Work + +- Depends on BEP-0002. +- Informs future relay, policy, and node coordination work. + +## Decision + +Pending. + +## References + +- RFC 9484 +- `burrow/src/daemon/` +- `burrow/src/control/` diff --git a/evolution/proposals/BEP-0004-hosted-mail-and-saas-identity.md b/evolution/proposals/BEP-0004-hosted-mail-and-saas-identity.md new file mode 100644 index 0000000..d633f37 --- /dev/null +++ b/evolution/proposals/BEP-0004-hosted-mail-and-saas-identity.md @@ -0,0 +1,68 @@ +# `BEP-0004` - Hosted Mail Backups and SaaS Identity + +```text +Status: Draft +Proposal: BEP-0004 +Authors: gpt-5.4 +Coordinator: gpt-5.4 +Reviewers: Pending +Constitution Sections: II, III, V +Implementation PRs: Pending +Decision Date: Pending +``` + +## Summary + +Burrow should start with hosted mail on Forward Email instead of self-hosting SMTP and IMAP on the first forge machine. Backup retention should still be controlled by Burrow through custom S3-compatible storage backed by Burrow-owned object storage. In parallel, Burrow should treat SaaS identity as a separate track and converge on Authentik as the long-term IdP, with Linear SAML SSO as a planned downstream integration rather than an immediate bootstrap dependency. + +## Motivation + +- The first forge host already carries source control, CI, and deployment bootstrap risk. Adding a self-hosted mail stack increases operational scope before the forge is stable. +- Forward Email already exposes SMTP and IMAP while allowing per-domain custom S3 backup storage, which preserves Burrow's data ownership over mailbox backups. +- The repository needs a durable decision record that separates hosted mail operations from future SaaS SSO work. + +## Detailed Design + +- Use Forward Email as the operational mail provider for `burrow.net` and `burrow.rs`. +- Configure custom S3-compatible storage per domain using Burrow-controlled object storage credentials. +- Keep one backup bucket per domain and enforce lifecycle expiration at the bucket layer. +- Add repository-owned tooling and documentation for applying and testing the Forward Email custom S3 configuration. +- Treat Authentik as the future identity authority for SaaS applications, but keep Linear SAML as a later rollout once the workspace and vendor prerequisites are available. Linear's current docs place SAML and SCIM behind higher-tier workspace security settings, so Burrow should treat plan availability as an explicit precondition. + +## Security and Operational Considerations + +- Forward Email API tokens and S3 credentials must stay in secret files and must not be passed directly on the shell command line. +- Buckets must remain private. Public bucket detection by the vendor should be treated as a hard failure, not a warning. +- Backup growth is unbounded without lifecycle rules. Retention policy is part of the rollout, not optional cleanup. +- Hosted mail reduces MTA attack surface on the forge host, but it adds third-party dependency risk; keeping backups in Burrow-owned storage limits that blast radius. + +## Contributor Playbook + +- Put the Forward Email API token in `intake/forwardemail_api_token.txt`. +- Use `Tools/forwardemail-custom-s3.sh` to configure `burrow.net` and `burrow.rs`. +- Run the helper again with `--test-only` after any credential rotation. +- Record the chosen endpoint, region, bucket names, and lifecycle policy alongside rollout evidence. +- Do not claim Linear SAML is live until the Authentik app, Linear workspace settings, workspace plan prerequisites, and end-to-end login flow are verified. + +## Alternatives Considered + +- Self-host Stalwart on the forge host immediately. Rejected for the first rollout because it expands host scope before source control and CI are stable. +- Rely on Forward Email default backup storage only. Rejected because it gives Burrow less control over retention and data location. +- Delay all SaaS identity planning until after forge cutover. Rejected because Linear and other SaaS integrations will otherwise accrete without an agreed authority. + +## Impact on Other Work + +- Narrows the first forge host scope. +- Creates a clean mail path for `contact@burrow.net` without requiring self-hosted SMTP and IMAP. +- Leaves Authentik and Linear SAML as explicit follow-up work instead of hidden assumptions. + +## Decision + +Pending. + +## References + +- `docs/FORWARDEMAIL.md` +- `Tools/forwardemail-custom-s3.sh` +- Forward Email FAQ: custom S3-compatible storage for backups +- Linear docs: SAML SSO diff --git a/tun/build.rs b/tun/build.rs index 8da8a40..03ee131 100644 --- a/tun/build.rs +++ b/tun/build.rs @@ -26,7 +26,7 @@ async fn generate(out_dir: &std::path::Path) -> anyhow::Result<()> { println!("cargo:rerun-if-changed={}", binary_path.to_str().unwrap()); if let (Ok(..), Ok(..)) = (File::open(&bindings_path), File::open(&binary_path)) { - return Ok(()) + return Ok(()); }; let archive = download(out_dir) diff --git a/tun/src/tokio/mod.rs b/tun/src/tokio/mod.rs index bd27109..f56f3d2 100644 --- a/tun/src/tokio/mod.rs +++ b/tun/src/tokio/mod.rs @@ -33,7 +33,7 @@ impl TunInterface { Ok(result) => return result, Err(_would_block) => { tracing::debug!("WouldBlock"); - continue + continue; } } } diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs index 0fc701e..66a2f15 100644 --- a/tun/src/unix/apple/mod.rs +++ b/tun/src/unix/apple/mod.rs @@ -114,6 +114,10 @@ impl TunInterface { ifname_to_string(buf) } + pub(crate) fn packet_information_size(&self) -> usize { + 4 + } + #[throws] #[instrument] fn ifreq(&self) -> sys::ifreq { diff --git a/tun/src/unix/linux/mod.rs b/tun/src/unix/linux/mod.rs index 03b6f09..9fc963a 100644 --- a/tun/src/unix/linux/mod.rs +++ b/tun/src/unix/linux/mod.rs @@ -73,6 +73,21 @@ impl TunInterface { ifname_to_string(iff.ifr_name) } + pub(crate) fn packet_information_size(&self) -> usize { + let mut iff = unsafe { mem::zeroed::() }; + match unsafe { sys::tun_get_iff(self.socket.as_raw_fd(), &mut iff) } { + Ok(_) => { + let flags = unsafe { iff.ifr_ifru.ifru_flags }; + if flags & libc::IFF_NO_PI as i16 != 0 { + 0 + } else { + 4 + } + } + Err(_) => 4, + } + } + #[throws] #[instrument] fn ifreq(&self) -> sys::ifreq { @@ -283,6 +298,16 @@ impl TunInterface { #[throws] #[instrument] pub fn send(&self, buf: &[u8]) -> usize { - self.socket.send(buf)? + let len = unsafe { + libc::write( + self.as_raw_fd(), + buf.as_ptr().cast::(), + buf.len(), + ) + }; + if len < 0 { + Err(Error::last_os_error())?; + } + len as usize } } diff --git a/tun/src/unix/mod.rs b/tun/src/unix/mod.rs index f1d7da1..ad25667 100644 --- a/tun/src/unix/mod.rs +++ b/tun/src/unix/mod.rs @@ -48,12 +48,26 @@ impl TunInterface { #[throws] #[instrument] pub fn recv(&self, buf: &mut [u8]) -> usize { - // Use IoVec to read directly into target buffer - let mut tmp_buf = [MaybeUninit::uninit(); 1500]; - let len = self.socket.recv(&mut tmp_buf)?; - let result_buf = unsafe { assume_init(&tmp_buf[4..len]) }; - buf[..len - 4].copy_from_slice(result_buf); - len - 4 + let packet_information_size = self.packet_information_size(); + let mut tmp_buf = [MaybeUninit::uninit(); 1504]; + let len = unsafe { + libc::read( + self.as_raw_fd(), + tmp_buf.as_mut_ptr().cast::(), + tmp_buf.len(), + ) + }; + if len < 0 { + Err(Error::last_os_error())?; + } + let len = len as usize; + if len < packet_information_size { + return 0; + } + + let result_buf = unsafe { assume_init(&tmp_buf[packet_information_size..len]) }; + buf[..len - packet_information_size].copy_from_slice(result_buf); + len - packet_information_size } #[throws] diff --git a/tun/tests/configure.rs b/tun/tests/configure.rs index 7c05959..bfa56ef 100644 --- a/tun/tests/configure.rs +++ b/tun/tests/configure.rs @@ -3,17 +3,33 @@ use std::{io::Error, net::Ipv4Addr}; use fehler::throws; use tun::TunInterface; +fn open_tun() -> Result, Error> { + match TunInterface::new() { + Ok(tun) => Ok(Some(tun)), + Err(err) + if err.kind() == std::io::ErrorKind::PermissionDenied + || matches!(err.raw_os_error(), Some(1 | 13)) => + { + eprintln!("skipping tun test without tunnel privileges: {err}"); + Ok(None) + } + Err(err) => Err(err), + } +} + #[test] #[throws] fn test_create() { - TunInterface::new()?; + let _ = open_tun()?; } #[test] #[throws] #[cfg(not(any(target_os = "windows", target_vendor = "apple")))] fn test_set_get_broadcast_addr() { - let tun = TunInterface::new()?; + let Some(tun) = open_tun()? else { + return Ok(()); + }; let addr = Ipv4Addr::new(10, 0, 0, 1); tun.set_ipv4_addr(addr)?; @@ -28,7 +44,9 @@ fn test_set_get_broadcast_addr() { #[throws] #[cfg(not(target_os = "windows"))] fn test_set_get_ipv4() { - let tun = TunInterface::new()?; + let Some(tun) = open_tun()? else { + return Ok(()); + }; let addr = Ipv4Addr::new(10, 0, 0, 1); tun.set_ipv4_addr(addr)?; @@ -43,7 +61,9 @@ fn test_set_get_ipv4() { fn test_set_get_ipv6() { use std::net::Ipv6Addr; - let tun = TunInterface::new()?; + let Some(tun) = open_tun()? else { + return Ok(()); + }; let addr = Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 1); tun.add_ipv6_addr(addr, 128)?; @@ -56,7 +76,9 @@ fn test_set_get_ipv6() { #[throws] #[cfg(not(target_os = "windows"))] fn test_set_get_mtu() { - let interf = TunInterface::new()?; + let Some(interf) = open_tun()? else { + return Ok(()); + }; interf.set_mtu(500)?; @@ -67,7 +89,9 @@ fn test_set_get_mtu() { #[throws] #[cfg(not(target_os = "windows"))] fn test_set_get_netmask() { - let interf = TunInterface::new()?; + let Some(interf) = open_tun()? else { + return Ok(()); + }; let netmask = Ipv4Addr::new(255, 0, 0, 0); let addr = Ipv4Addr::new(192, 168, 1, 1); diff --git a/tun/tests/tokio.rs b/tun/tests/tokio.rs index 097387c..3b89777 100644 --- a/tun/tests/tokio.rs +++ b/tun/tests/tokio.rs @@ -1,10 +1,27 @@ #[cfg(all(feature = "tokio", not(target_os = "windows")))] use std::net::Ipv4Addr; +#[cfg(all(feature = "tokio", not(target_os = "windows")))] +fn open_tun() -> Option { + match tun::TunInterface::new() { + Ok(tun) => Some(tun), + Err(err) + if err.kind() == std::io::ErrorKind::PermissionDenied + || matches!(err.raw_os_error(), Some(1 | 13)) => + { + eprintln!("skipping tokio tun test without tunnel privileges: {err}"); + None + } + Err(err) => panic!("failed to create tun interface: {err}"), + } +} + #[tokio::test] #[cfg(all(feature = "tokio", not(target_os = "windows")))] async fn test_create() { - let tun = tun::TunInterface::new().unwrap(); + let Some(tun) = open_tun() else { + return; + }; let _ = tun::tokio::TunInterface::new(tun).unwrap(); } @@ -12,7 +29,9 @@ async fn test_create() { #[ignore = "requires interactivity"] #[cfg(all(feature = "tokio", not(target_os = "windows")))] async fn test_write() { - let tun = tun::TunInterface::new().unwrap(); + let Some(tun) = open_tun() else { + return; + }; tun.set_ipv4_addr(Ipv4Addr::from([192, 168, 1, 10])) .unwrap(); let async_tun = tun::tokio::TunInterface::new(tun).unwrap();