diff --git a/.github/actions/checkout-ipstack/action.yml b/.github/actions/checkout-ipstack/action.yml new file mode 100644 index 00000000..888e4f01 --- /dev/null +++ b/.github/actions/checkout-ipstack/action.yml @@ -0,0 +1,9 @@ +name: Checkout sibling IP stack +description: Fetch the exact fork revision required by the path dependency +runs: + using: composite + steps: + - shell: bash + run: | + git clone --no-checkout https://github.com/acking-you/ipstack.git ../ipstack + git -C ../ipstack checkout 99ea5315aff12d632523f8e32d3fa1ea8cab5081 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 689024b4..13b9a278 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -25,8 +25,10 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ./.github/actions/checkout-ipstack - uses: dtolnay/rust-toolchain@stable with: + toolchain: nightly-2026-01-26 components: clippy, rustfmt - name: rustfmt @@ -35,18 +37,21 @@ jobs: - name: check if: ${{ !cancelled() }} - run: cargo check --verbose + run: cargo check --verbose --locked - name: clippy if: ${{ !cancelled() }} - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --locked --all-targets --all-features -- -D warnings - name: Build if: ${{ !cancelled() }} run: | - cargo build --verbose --tests --all-features + cargo build --locked --verbose --tests --all-features cargo clean - cargo build --verbose + cargo build --locked --verbose + + - name: Test + run: cargo test --locked --all-features - name: Abort on error if: ${{ failure() }} @@ -59,21 +64,23 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ./.github/actions/checkout-ipstack - uses: dtolnay/rust-toolchain@stable with: + toolchain: nightly-2026-01-26 components: clippy, rustfmt - name: Install cargo ndk and rust compiler for android target if: ${{ !cancelled() }} run: | - cargo install --locked cargo-ndk + cargo +stable install --locked cargo-ndk rustup target add x86_64-linux-android - name: clippy if: ${{ !cancelled() }} - run: cargo ndk -t x86_64 clippy --all-features -- -D warnings + run: cargo ndk -t x86_64 clippy --locked --all-features -- -D warnings - name: Build if: ${{ !cancelled() }} run: | - cargo ndk -t x86_64 rustc --verbose --all-features --lib --crate-type=cdylib + cargo ndk -t x86_64 rustc --locked --verbose --all-features --lib --crate-type=cdylib - name: Abort on error if: ${{ failure() }} run: echo "Android build job failed" && false @@ -85,21 +92,23 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: ./.github/actions/checkout-ipstack - uses: dtolnay/rust-toolchain@stable with: + toolchain: nightly-2026-01-26 components: clippy, rustfmt - - name: Install cargo lipo and rust compiler for ios target + - name: Install rust compiler for iOS targets if: ${{ !cancelled() }} run: | - cargo install --locked cargo-lipo rustup target add x86_64-apple-ios aarch64-apple-ios - name: clippy if: ${{ !cancelled() }} - run: cargo clippy --target x86_64-apple-ios --all-features -- -D warnings + run: cargo clippy --locked --target x86_64-apple-ios --all-features -- -D warnings - name: Build if: ${{ !cancelled() }} run: | - cargo lipo --verbose --all-features + cargo build --locked --target aarch64-apple-ios --lib --all-features + cargo build --locked --target x86_64-apple-ios --lib --all-features - name: Abort on error if: ${{ failure() }} run: echo "iOS build job failed" && false @@ -113,6 +122,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - uses: ./.github/actions/checkout-ipstack - uses: dtolnay/rust-toolchain@stable - name: Check semver if: ${{ !cancelled() }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8a1cb9b..6e3f1532 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,10 @@ jobs: if: github.actor != 'dependabot[bot]' && github.actor != 'github-actions[bot]' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request_target' && !github.event.pull_request.head.repo.fork && contains(github.event.pull_request.labels.*.name, 'safe to test'))) steps: - uses: actions/checkout@v4 + - uses: ./.github/actions/checkout-ipstack - uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly-2026-01-26 - name: Populate .env env: DOTENV: ${{ secrets.DOTENV }} @@ -38,7 +41,7 @@ jobs: pip install -r tests/requirements.txt - name: Build project - run: cargo build --release + run: cargo build --release --locked - name: Run tests run: | diff --git a/.gitignore b/.gitignore index e163239e..94ef07ee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,5 @@ xcuserdata/ .VSCodeCounter/ build/ tmp/ -Cargo.lock target/ .idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..f7ada317 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2877 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter 0.1.4", + "log", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64easy" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a336b00b6906085a0c12756be49e24698b3ce6b0dfbe517029be8cee05f84b" + +[[package]] +name = "bincode-next" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6626829353ae29293be5c86f42de5f0468bc758af074b0c7d08f07e538ccbc" +dependencies = [ + "bincode_derive-next", + "pastey", + "rapidhash", + "serde", + "unty-next", +] + +[[package]] +name = "bincode_derive-next" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409532b1da3be643427e49c2f79e0c47d445f695e1b028270d9e80e9044d7d1c" +dependencies = [ + "virtue-next", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "shlex 2.0.1", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cidr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579504560394e388085d0c080ea587dfa5c15f7e251b4d5247d1e1a61d1d6928" +dependencies = [ + "serde", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc2" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a800fa00a7015b2989b7f6186bc5a669ccd839204d73625aa6e706765a26c2d5" +dependencies = [ + "log", + "nix 0.31.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "daemonize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" +dependencies = [ + "libc", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "digest_auth" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3054f4e81d395e50822796c5e99ca522e6ba7be98947d6d4b0e5e61640bdb894" +dependencies = [ + "digest", + "hex", + "md-5", + "rand 0.8.7", + "sha2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter 2.0.0", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etherparse" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17304d06addb3283cdc4bd528e42dd95e73c8ee2d6492ffce415e93660885449" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.1", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "rand 0.10.2", + "ring", + "thiserror 2.0.21", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "internet-checksum" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6d6206008e25125b1f97fbe5d309eb7b85141cf9199d52dbd3729a1584dd16" + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "ipstack" +version = "1.0.2-dev" +dependencies = [ + "ahash", + "etherparse", + "log", + "rand 0.10.2", + "serde_json", + "thiserror 2.0.21", + "tokio", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" +dependencies = [ + "defmt", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "mac-addr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "netdev" +version = "0.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c52c2584961c68f5a6e3356797344061f818c93b8acc9cd6d7e284795e563e" +dependencies = [ + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core 0.9.0", + "netlink-packet-route", + "netlink-sys 0.9.0", + "objc2-core-foundation", + "objc2-system-configuration", + "once_cell", + "plist", + "windows-sys 0.61.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6e2daf9a8c2e21302714706860a0e6be02e03d17294ecc66b354ea7d4059dd" + +[[package]] +name = "netlink-packet-route" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d48ffc8b73a506e1ff0878528c72668f2bfbc3d875b6be890a96be5d0d5576" +dependencies = [ + "bitflags 2.13.1", + "libc", + "log", + "netlink-packet-core 0.9.0", + "zerocopy", +] + +[[package]] +name = "netlink-packet-sock-diag" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a495cb1de50560a7cd12fdcf023db70eec00e340df81be31cedbbfd4aadd6b76" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core 0.7.0", + "netlink-packet-utils", + "smallvec", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97e03ae6247c6cdb499e8f14e98c72b83954d85822b687056e3a21150b438f2b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "log", + "netlink-packet-core 0.9.0", + "netlink-sys 0.9.0", + "thiserror 2.0.21", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "libc", + "log", +] + +[[package]] +name = "netlink-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99d38e00b420df49e940fe0826e667c3dad82ac68eb4c2bbf754c1c14a83a10" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "netstat2" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496f264d3ead4870d6b366deb9d20597592d64aac2a907f3e7d07c2325ba4663" +dependencies = [ + "bindgen", + "bitflags 2.13.1", + "byteorder", + "netlink-packet-core 0.7.0", + "netlink-packet-sock-diag", + "netlink-packet-utils", + "netlink-sys 0.8.8", + "num-derive", + "num-traits", + "thiserror 2.0.21", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "block2", + "dispatch2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "bitflags 2.13.1", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "plist" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rtnetlink" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f60a3ee149005c5819e03e3d73ba4a470bc18b52b0c8422cfe1bea2cae63c6" +dependencies = [ + "futures-channel", + "futures-util", + "log", + "netlink-packet-core 0.9.0", + "netlink-packet-route", + "netlink-proto", + "netlink-sys 0.9.0", + "nix 0.30.1", + "thiserror 2.0.21", + "tokio", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "socks5-impl" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06ffc60fe8f46f21a90e7a331289c26753eef83074ea248e5302f9d196d6216" +dependencies = [ + "async-trait", + "bytes", + "percent-encoding", + "serde", + "thiserror 2.0.21", + "tokio", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-configuration" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501336eb7ba9e417300a6a0fa985721065467aa83a6dcf0422a8e43e4c0328fa" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "system-configuration-sys", +] + +[[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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tproxy-config" +version = "7.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfdd43d2e88799edd5e64b3716119b6eedd74542ecea3c5a94625a2257eee0c" +dependencies = [ + "cidr", + "futures", + "libloading 0.9.0", + "log", + "nix 0.31.3", + "regex", + "resolv-conf", + "rtnetlink", + "serde", + "serde_json", + "system-configuration", + "tempfile", + "tokio", + "windows-sys 0.61.2", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tun" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb55e468c585e02ef89ba7a1a9848871ac942dfc64547ad4d1166b0f61a98be" +dependencies = [ + "bytes", + "cfg-if", + "futures", + "futures-core", + "ipnet", + "libc", + "log", + "nix 0.31.3", + "serde_json", + "thiserror 2.0.21", + "tokio", + "tokio-util", + "windows-sys 0.61.2", + "wintun-bindings", +] + +[[package]] +name = "tun2proxy" +version = "0.9.0" +dependencies = [ + "android_logger", + "async-trait", + "base64easy", + "bincode-next", + "chrono", + "clap", + "ctrlc2", + "daemonize", + "digest_auth", + "dotenvy", + "env_logger", + "hashbrown", + "hashlink", + "hickory-proto", + "httparse", + "internet-checksum", + "ipstack", + "jni 0.22.4", + "log", + "netdev", + "netstat2", + "nix 0.31.3", + "percent-encoding", + "serde", + "serde_json", + "shlex 2.0.1", + "socket2", + "socks5-impl", + "thiserror 2.0.21", + "tokio", + "tokio-util", + "tproxy-config", + "tun", + "unicase", + "url", + "windows-service", + "windows-sys 0.61.2", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicase" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16062d030850f35054e37746427b9febb74a2f24c9c6dd6fa2d0c13c5f53221e" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue-next" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5208a9710a6ca1c2da0d64289a5dcca13deb05afdedf651f4e11bcd9fc53eb" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-service" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857224b3b211c6f3616921f081ee54721ee3ad2ace2fac6a6337e032f7b4dcf2" +dependencies = [ + "bitflags 2.13.1", + "widestring", + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wintun-bindings" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4494d02357537af05cf526be7b817a51752b688a78926af57379abd840d911" +dependencies = [ + "blocking", + "futures", + "libloading 0.9.0", + "log", + "thiserror 2.0.21", + "windows-sys 0.61.2", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 64c22f0f..5a9f39c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,11 +74,16 @@ bincode-next = "3.1.1" # these here avoids pulling them (and breaking the build) on iOS/Android, where # the platform VPN API owns per-application routing instead. [target.'cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))'.dependencies] -netdev = "0.46.3" netstat2 = "0.11.2" socket2 = "0.6.5" +[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] +netdev = "0.46.3" + [target.'cfg(target_os="windows")'.dependencies] +# Gateway MAC discovery sends synchronous ARP probes, including to TUN portals. +# Route selection and DNS only need passive IP Helper snapshots on Windows. +netdev = { version = "0.46.3", default-features = false } windows-service = "0.8.1" windows-sys = { version = "0.61", features = [ "Win32_Foundation", diff --git a/README.md b/README.md index 5a3c38f0..80eeae26 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,18 @@ # tun2proxy A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows. +This fork supports desktop `--dns physical`: query AliDNS `223.5.5.5`, Tencent +DNSPod `119.29.29.29`, AliDNS `223.6.6.6`, then the physical adapter and configured +resolver in order. DNS UDP and TCP sockets bind to that interface. Explicit +process bypass uses this same resolver path, including cached Fake-IP recovery. +Platform-managed VPN providers retain their existing DNS modes. On Windows, +interface selection reads IP Helper snapshots without gateway MAC/ARP probing. + +Embedders can reuse `LocalProcessMatcher` for local HTTP/SOCKS connections. It +matches only loopback callers and excludes the embedding process's own PID +before ancestry matching, preserving TUN-to-proxy routing. UDP relays preserve +empty and large datagrams and log route, socket endpoints and lifecycle counts. + [![Crates.io](https://img.shields.io/crates/v/tun2proxy.svg)](https://crates.io/crates/tun2proxy) [![tun2proxy](https://docs.rs/tun2proxy/badge.svg)](https://docs.rs/tun2proxy) [![Documentation](https://img.shields.io/badge/docs-release-brightgreen.svg?style=flat)](https://docs.rs/tun2proxy) diff --git a/src/args.rs b/src/args.rs index 434d8fd0..f09e16fd 100644 --- a/src/args.rs +++ b/src/args.rs @@ -420,6 +420,7 @@ impl std::fmt::Display for ArgVerbosity { /// - Virtual: Use a virtual DNS server to handle DNS queries, also known as Fake-IP mode /// - OverTcp: Use TCP to send DNS queries to the DNS server /// - Direct: Do not handle DNS by relying on DNS server bypassing +/// - Physical: Resolve through ordered local resolvers on the physical interface (desktop only) #[repr(C)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, serde::Serialize, serde::Deserialize)] pub enum ArgDns { @@ -427,6 +428,7 @@ pub enum ArgDns { OverTcp, #[default] Direct, + Physical, } #[cfg(target_os = "android")] diff --git a/src/direct.rs b/src/direct.rs index 7ad8f980..d739ff3e 100644 --- a/src/direct.rs +++ b/src/direct.rs @@ -29,7 +29,7 @@ const DNS_TCP_TIMEOUT: Duration = Duration::from_millis(1500); /// The physical interface direct relays egress through. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct BindInterface { +pub struct BindInterface { /// IPv4 interface index, used by Windows `IP_UNICAST_IF` and macOS /// `IP_BOUND_IF`. pub ipv4_index: u32, @@ -59,11 +59,13 @@ impl BindInterface { .map(|network| network.addr()) .find(|address| !address.is_unspecified() && !address.is_loopback()); #[cfg(target_os = "windows")] - let (ipv4_index, ipv6_index) = windows_interface_indices(&iface)?; + let (ipv4_index, ipv6_index, dns_servers) = windows_interface_details(&iface)?; // Both BSD-style socket options take the same `if_nametoindex` value for // either family, so one index serves both. #[cfg(any(target_os = "linux", target_os = "macos"))] let (ipv4_index, ipv6_index) = (iface.index, iface.index); + #[cfg(any(target_os = "linux", target_os = "macos"))] + let dns_servers = iface.dns_servers; if ipv4_index == 0 && ipv6_index == 0 { return Err(format!("network interface `{}` has an invalid index 0", iface.name).into()); @@ -72,19 +74,19 @@ impl BindInterface { ipv4_index, ipv6_index, ipv4_addr, - dns_servers: iface.dns_servers, + dns_servers, name: iface.name, }) } } #[cfg(target_os = "windows")] -fn windows_interface_indices(iface: &netdev::Interface) -> std::io::Result<(u32, u32)> { +fn windows_interface_details(iface: &netdev::Interface) -> std::io::Result<(u32, u32, Vec)> { use std::ffi::CStr; use windows_sys::Win32::{ Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS}, NetworkManagement::IpHelper::{GetAdaptersAddresses, IP_ADAPTER_ADDRESSES_LH}, - Networking::WinSock::AF_UNSPEC, + Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR_IN, SOCKADDR_IN6}, }; let mut size = 0_u32; @@ -111,7 +113,27 @@ fn windows_interface_indices(iface: &netdev::Interface) -> std::io::Result<(u32, Some(unsafe { CStr::from_ptr(adapter.AdapterName.cast()) }.to_string_lossy()) }; if (iface.index != 0 && ipv4_index == iface.index) || adapter_name.as_deref() == Some(iface.name.as_str()) { - return Ok((ipv4_index, adapter.Ipv6IfIndex)); + let mut dns_servers = Vec::new(); + let mut dns = adapter.FirstDnsServerAddress; + while !dns.is_null() { + // SAFETY: nodes and addresses belong to the live, aligned + // GetAdaptersAddresses buffer. Check family-specific lengths + // before reading; an IPv4 address is shorter than SOCKADDR_IN6. + let row = unsafe { &*dns }; + let address = row.Address; + if !address.lpSockaddr.is_null() && address.iSockaddrLength >= 2 { + let family = unsafe { std::ptr::read_unaligned(address.lpSockaddr.cast::()) }; + if family == AF_INET && address.iSockaddrLength as usize >= std::mem::size_of::() { + let address = unsafe { std::ptr::read_unaligned(address.lpSockaddr.cast::()) }; + dns_servers.push(IpAddr::V4(unsafe { address.sin_addr.S_un.S_addr }.to_ne_bytes().into())); + } else if family == AF_INET6 && address.iSockaddrLength as usize >= std::mem::size_of::() { + let address = unsafe { std::ptr::read_unaligned(address.lpSockaddr.cast::()) }; + dns_servers.push(IpAddr::V6(unsafe { address.sin6_addr.u.Byte }.into())); + } + } + dns = row.Next; + } + return Ok((ipv4_index, adapter.Ipv6IfIndex, dns_servers)); } current = adapter.Next; } @@ -155,7 +177,10 @@ fn is_unusable_egress(iface: &netdev::Interface, tun_name: Option<&str>) -> bool /// Score a candidate so the best physical interface wins. Higher is better. fn egress_preference(iface: &netdev::Interface) -> (u8, u8, u8) { + #[cfg(not(target_os = "windows"))] let has_gateway = u8::from(iface.gateway.is_some()); + #[cfg(target_os = "windows")] + let has_gateway = 0; let physical = u8::from(iface.is_physical()); let usable_address = u8::from( iface @@ -173,7 +198,7 @@ fn egress_preference(iface: &netdev::Interface) -> (u8, u8, u8) { /// `tun_name` names this session's TUN device so it can never be selected. Pass /// it even before the device exists: the address and type checks then still /// reject a leftover device from a previous session. -pub(crate) fn detect(manual: Option<&str>, tun_name: Option<&str>) -> crate::Result { +pub fn detect(manual: Option<&str>, tun_name: Option<&str>) -> crate::Result { match manual { Some(wanted) => { let iface = netdev::get_interfaces() @@ -190,7 +215,13 @@ pub(crate) fn detect(manual: Option<&str>, tun_name: Option<&str>) -> crate::Res } None => { #[cfg(target_os = "windows")] - if let Some(index) = crate::windows_network_config::physical_default_interface(tun_name) + let candidates = netdev::get_interfaces() + .into_iter() + .filter(|iface| !is_unusable_egress(iface, tun_name)) + .map(|iface| iface.index) + .collect::>(); + #[cfg(target_os = "windows")] + if let Some(index) = crate::windows_network_config::physical_default_interface(tun_name, &candidates) && let Some(iface) = netdev::get_interfaces() .into_iter() .find(|iface| iface.index == index && !is_unusable_egress(iface, tun_name)) @@ -201,9 +232,11 @@ pub(crate) fn detect(manual: Option<&str>, tun_name: Option<&str>) -> crate::Res // address it would use for an arbitrary address, not by reading the // routing table. While TUN capture routes are installed that probe // resolves to the TUN itself, so the answer has to be filtered. + #[cfg(not(target_os = "windows"))] let candidate = netdev::get_default_interface() .ok() .filter(|iface| !is_unusable_egress(iface, tun_name)); + #[cfg(not(target_os = "windows"))] if let Some(iface) = candidate { return BindInterface::from_netdev(iface); } @@ -343,7 +376,7 @@ fn apply_device_bind(socket: &Socket, peer: SocketAddr, iface: &BindInterface) - } /// Open a TCP connection to `peer` that egresses through `iface`. -pub(crate) async fn connect_tcp_bound(peer: SocketAddr, iface: &BindInterface) -> std::io::Result { +pub async fn connect_tcp_bound(peer: SocketAddr, iface: &BindInterface) -> std::io::Result { let domain = if peer.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 }; let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?; apply_device_bind(&socket, peer, iface)?; @@ -367,7 +400,7 @@ pub(crate) async fn connect_tcp_bound(peer: SocketAddr, iface: &BindInterface) - } /// Open a connected UDP socket to `peer` that egresses through `iface`. -pub(crate) fn bind_udp_bound(peer: SocketAddr, iface: &BindInterface) -> std::io::Result { +pub fn bind_udp_bound(peer: SocketAddr, iface: &BindInterface) -> std::io::Result { let domain = if peer.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 }; let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?; apply_device_bind(&socket, peer, iface)?; @@ -406,6 +439,27 @@ pub(crate) fn bind_udp_bound(peer: SocketAddr, iface: &BindInterface) -> std::io tokio::net::UdpSocket::from_std(std_socket) } +/// Bind an unconnected UDP relay to a physical interface. Destination filtering +/// and response ownership remain the caller's responsibility. +pub fn bind_udp_unconnected(ipv6: bool, iface: &BindInterface) -> std::io::Result { + let address = if ipv6 { + SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, 0)) + } else { + SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)) + }; + let socket = Socket::new(if ipv6 { Domain::IPV6 } else { Domain::IPV4 }, Type::DGRAM, Some(Protocol::UDP))?; + if ipv6 { + socket.set_only_v6(true)?; + socket.set_multicast_if_v6(iface.ipv6_index)?; + } else if let Some(address) = iface.ipv4_addr { + socket.set_multicast_if_v4(&address)?; + } + apply_device_bind(&socket, address, iface)?; + socket.bind(&SockAddr::from(address))?; + socket.set_nonblocking(true)?; + tokio::net::UdpSocket::from_std(socket.into()) +} + /// Resolve a virtual-DNS name through a DNS socket pinned to the physical /// interface. /// @@ -414,7 +468,7 @@ pub(crate) fn bind_udp_bound(peer: SocketAddr, iface: &BindInterface) -> std::io /// packet through `IP_UNICAST_IF`/`SO_BINDTODEVICE` guarantees that a process /// selected for bypass can recover from a fake address cached before the policy /// was changed. -pub(crate) async fn resolve_domain_bound( +pub async fn resolve_domain_bound( domain: &str, port: u16, dns_server: IpAddr, @@ -466,11 +520,25 @@ async fn query_dns_bound( configured_dns: IpAddr, iface: &BindInterface, ) -> std::io::Result { - let servers = direct_dns_servers(configured_dns, iface); + let servers = direct_dns_servers(configured_dns, iface) + .into_iter() + .map(|ip| SocketAddr::new(ip, 53)) + .collect::>(); + query_dns_servers(request, request_id, expected_query, domain, &servers, iface).await +} + +async fn query_dns_servers( + request: &[u8], + request_id: u16, + expected_query: &hickory_proto::op::Query, + domain: &str, + servers: &[SocketAddr], + iface: &BindInterface, +) -> std::io::Result { let mut last_error = None; - 'servers: for dns_server in &servers { - let server = SocketAddr::new(*dns_server, 53); + 'servers: for dns_server in servers { + let server = *dns_server; for _ in 0..DNS_UDP_ATTEMPTS { match tokio::time::timeout(DNS_UDP_TIMEOUT, query_dns_udp(request, request_id, expected_query, server, iface)).await { Ok(Ok(response)) if response.truncation => break, @@ -537,19 +605,45 @@ async fn query_dns_bound( fn direct_dns_servers(configured_dns: IpAddr, iface: &BindInterface) -> Vec { let mut servers = Vec::new(); - for address in iface.dns_servers.iter().copied().filter(is_usable_dns_server) { + // The shared Windows DNS service hides the requesting process. Resolve the + // portal locally as well as bypass names, so its cache cannot mix remote + // CDN answers into direct connections. Keep a custom resolver as fallback. + for address in PREFERRED_DNS_SERVERS + .into_iter() + .chain(iface.dns_servers.iter().copied()) + .chain([configured_dns]) + .filter(is_usable_dns_server) + { if !servers.contains(&address) { servers.push(address); } } - if is_usable_dns_server(&configured_dns) && !servers.contains(&configured_dns) { - servers.push(configured_dns); - } servers } -pub(crate) fn preferred_dns_server(configured_dns: IpAddr, iface: &BindInterface) -> Option { - direct_dns_servers(configured_dns, iface).into_iter().next() +/// Ordered local resolvers, shared by portal and process-bypass lookups. +pub const PREFERRED_DNS_SERVERS: [IpAddr; 3] = [ + IpAddr::V4(Ipv4Addr::new(223, 5, 5, 5)), + IpAddr::V4(Ipv4Addr::new(119, 29, 29, 29)), + IpAddr::V4(Ipv4Addr::new(223, 6, 6, 6)), +]; + +/// Forward one validated DNS message through the physical interface. A failed +/// UDP query retries over TCP before moving to the next resolver; NXDOMAIN is +/// authoritative and does not trigger fallback. No system resolver is involved. +pub async fn query_bound(request: &[u8], configured_dns: IpAddr, iface: &BindInterface) -> std::io::Result> { + let message = hickory_proto::op::Message::from_vec(request).map_err(std::io::Error::other)?; + let query = crate::dns::validate_dns_query(&message).map_err(std::io::Error::other)?; + let name = query.name().to_ascii(); + let started = std::time::Instant::now(); + let response = query_dns_bound(request, message.id, query, &name, configured_dns, iface).await?; + log::debug!( + "Physical DNS {name} {:?} via {iface}: {:?}, elapsed={:?}", + query.query_type(), + response.response_code, + started.elapsed() + ); + response.to_vec().map_err(std::io::Error::other) } fn is_usable_dns_server(address: &IpAddr) -> bool { @@ -699,6 +793,7 @@ mod tests { assert_ne!(selected.ipv4_addr.map(IpAddr::V4), Some(tproxy_config::TUN_IPV4)); assert!(selected.ipv4_index != 0 || selected.ipv6_index != 0); + #[cfg(not(target_os = "windows"))] if let Ok(os_default) = netdev::get_default_interface() && is_unusable_egress(&os_default, Some(tproxy_config::TUN_NAME)) { @@ -749,12 +844,15 @@ mod tests { } #[test] - fn direct_dns_prefers_physical_interface_resolvers() { + fn direct_dns_prefers_ali_and_tencent_then_adapter_and_custom_fallback() { let physical = "192.168.21.1".parse().unwrap(); let configured = "8.8.8.8".parse().unwrap(); let iface = test_interface(vec![physical]); - assert_eq!(direct_dns_servers(configured, &iface), vec![physical, configured]); + assert_eq!( + direct_dns_servers(configured, &iface), + PREFERRED_DNS_SERVERS.into_iter().chain([physical, configured]).collect::>() + ); } #[test] @@ -762,7 +860,10 @@ mod tests { let configured = "1.1.1.1".parse().unwrap(); let iface = test_interface(vec![configured]); - assert_eq!(direct_dns_servers(configured, &iface), vec![configured]); + assert_eq!( + direct_dns_servers(configured, &iface), + PREFERRED_DNS_SERVERS.into_iter().chain([configured]).collect::>() + ); } #[test] @@ -774,15 +875,66 @@ mod tests { "224.0.0.251".parse().unwrap(), ]); - assert_eq!(direct_dns_servers(configured, &iface), vec![configured]); + assert_eq!( + direct_dns_servers(configured, &iface), + PREFERRED_DNS_SERVERS.into_iter().chain([configured]).collect::>() + ); } - #[test] - fn direct_dns_selects_the_physical_resolver_for_private_virtual_portals() { - let physical = "192.168.21.1".parse().unwrap(); - let configured = "8.8.8.8".parse().unwrap(); - let iface = test_interface(vec![physical]); - - assert_eq!(preferred_dns_server(configured, &iface), Some(physical)); + #[tokio::test] + async fn bound_dns_retries_truncated_udp_over_tcp_and_honors_resolver_order() { + use hickory_proto::op::{Message, MessageType, OpCode, Query, ResponseCode}; + use hickory_proto::rr::{Name, RecordType}; + let iface = BindInterface::from_netdev(netdev::get_interfaces().into_iter().find(|iface| iface.is_loopback()).unwrap()).unwrap(); + let first = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap(); + // Windows TCP/UDP excluded-port ranges can differ. Ask each protocol + // for a free ephemeral port and keep the first pair both can bind. + let mut pair = None; + for _ in 0..32 { + let udp = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap(); + match tokio::net::TcpListener::bind(udp.local_addr().unwrap()).await { + Ok(tcp) => { + pair = Some((tcp, udp)); + break; + } + Err(error) if matches!(error.kind(), std::io::ErrorKind::AddrInUse | std::io::ErrorKind::PermissionDenied) => {} + Err(error) => panic!("cannot bind loopback DNS test peer: {error}"), + } + } + let (second, second_udp) = pair.expect("a shared TCP/UDP ephemeral test port"); + let servers = [first.local_addr().unwrap(), second.local_addr().unwrap()]; + let question = Query::query(Name::from_str("example.test.").unwrap(), RecordType::A); + let mut request = Message::new(42, MessageType::Query, OpCode::Query); + request.add_query(question.clone()); + let request = request.to_vec().unwrap(); + tokio::time::timeout(Duration::from_secs(5), async { + let peers = async { + let mut buffer = [0; 4096]; + let (len, client) = first.recv_from(&mut buffer).await.unwrap(); + let mut response = Message::from_vec(&buffer[..len]).unwrap(); + response.metadata.message_type = MessageType::Response; + response.metadata.response_code = ResponseCode::ServFail; + first.send_to(&response.to_vec().unwrap(), client).await.unwrap(); + let (len, client) = second_udp.recv_from(&mut buffer).await.unwrap(); + let mut response = Message::from_vec(&buffer[..len]).unwrap(); + response.metadata.message_type = MessageType::Response; + response.metadata.truncation = true; + second_udp.send_to(&response.to_vec().unwrap(), client).await.unwrap(); + let (mut tcp, _) = second.accept().await.unwrap(); + let len = tcp.read_u16().await.unwrap() as usize; + tcp.read_exact(&mut buffer[..len]).await.unwrap(); + let mut response = Message::from_vec(&buffer[..len]).unwrap(); + response.metadata.message_type = MessageType::Response; + response.metadata.response_code = ResponseCode::NXDomain; + let response = response.to_vec().unwrap(); + tcp.write_u16(response.len() as u16).await.unwrap(); + tcp.write_all(&response).await.unwrap(); + }; + let lookup = query_dns_servers(&request, 42, &question, "example.test", &servers, &iface); + let (_, response) = tokio::join!(peers, lookup); + assert_eq!(response.unwrap().response_code, ResponseCode::NXDomain); + }) + .await + .unwrap(); } } diff --git a/src/general_api.rs b/src/general_api.rs index ca53a906..cfb4cd3e 100644 --- a/src/general_api.rs +++ b/src/general_api.rs @@ -225,7 +225,13 @@ async fn general_run_async_with_process_bypass_inner( /// Returns either way. `direct::detect` rejects tunnels by name, type, and /// address regardless, so this only improves *which* physical interface is /// chosen; it is not what makes the selection correct. -#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] +#[cfg(target_os = "windows")] +async fn wait_for_physical_default_route(_tun_name: Option<&str>) { + // Windows reads the route table and filters tunnel interfaces directly; + // waiting for a source-address probe to stop choosing TUN is unnecessary. +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] async fn wait_for_physical_default_route(tun_name: Option<&str>) { const SETTLE_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(2000); const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100); diff --git a/src/lib.rs b/src/lib.rs index 9a1d20fa..8081a0fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub const DEFAULT_MTU: u16 = 1500; mod android; mod args; #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] -mod direct; +pub mod direct; mod directions; mod dns; mod dump_logger; @@ -78,7 +78,11 @@ pub mod icmp; mod network_config; mod no_proxy; #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] +mod physical_dns; +#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] mod process; +#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] +pub use process::{LocalProcessMatcher, SessionPolicy}; mod process_bypass; mod proxy_handler; mod session_info; @@ -610,7 +614,7 @@ pub async fn run_with_system_managed_network( where D: AsyncRead + AsyncWrite + Unpin + Send + 'static, { - if args.setup || !args.bypass_process.is_empty() || args.bind_interface.is_some() { + if args.setup || !args.bypass_process.is_empty() || args.bind_interface.is_some() || args.dns == ArgDns::Physical { return Err("system-managed packet tunnels cannot configure routes, bind an interface or match processes".into()); } run_with_preselected_egress( @@ -644,6 +648,10 @@ where { log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), version_info!()); log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); + #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] + if args.dns == ArgDns::Physical { + return Err("physical DNS requires a desktop physical interface".into()); + } let server_addr = args.proxy.addr; let key = args.proxy.credentials.clone(); @@ -930,6 +938,27 @@ where #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] let bypass = false; + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + if info.dst.port() == DNS_PORT && (dns == ArgDns::Physical || bypass) { + let result = run_until_process_policy_change( + async { + match direct_bind.as_deref() { + Some(iface) => physical_dns::tcp(&mut tcp, dns_addr, iface, ipv6_enabled).await, + None => Err(std::io::Error::other("physical DNS interface unavailable")), + } + }, + policy, + ) + .await; + if let Some(Err(error)) = result { + let _ = tcp.abort(); + log::warn!("{info} physical DNS TCP failed: {error}"); + } else if result.is_none() { + reset_tcp_after_policy_change(&mut tcp, info); + } + return; + } + // Windows and platform VPNs advertise a private DNS portal. // TCP fallback must use the same upstream resolver as UDP // DNS; connecting the portal through SOCKS would loop/fail. @@ -970,25 +999,10 @@ where #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] let (handler_result, bind): (HandlerResult, Option) = { let mut info = info; - let dns_destination = if bypass && info.dst.port() == DNS_PORT && is_private_ip(info.dst.ip()) { - direct_bind - .as_deref() - .and_then(|bind| direct::preferred_dns_server(dns_addr, bind)) - .ok_or_else(|| std::io::Error::new(ErrorKind::NotFound, "no usable physical DNS resolver")) - .map(Some) + let bypass_destination = if bypass { + restore_bypass_destination(&mut info, virtual_dns.as_ref(), dns_addr, direct_bind.as_ref()).await } else { - Ok(None) - }; - let bypass_destination = match dns_destination { - Ok(Some(resolver)) => { - info.dst.set_ip(resolver); - restore_bypass_destination(&mut info, virtual_dns.as_ref(), dns_addr, direct_bind.as_ref()).await - } - Ok(None) if bypass => { - restore_bypass_destination(&mut info, virtual_dns.as_ref(), dns_addr, direct_bind.as_ref()).await - } - Ok(None) => Ok(()), - Err(error) => Err(error), + Ok(()) }; let domain_name = resolve_virtual_domain(virtual_dns.as_ref(), info.dst.ip()).await; match (bypass_destination, domain_name, bypass) { @@ -1092,15 +1106,13 @@ where } let relay = async { + #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + if info.dst.port() == DNS_PORT && (dns == ArgDns::Physical || bypass) { + let iface = direct_bind.ok_or_else(|| std::io::Error::other("physical DNS interface unavailable"))?; + return physical_dns::udp(udp, dns_addr, iface, ipv6_enabled).await.map_err(Into::into); + } #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] if bypass { - if info.dst.port() == DNS_PORT && is_private_ip(info.dst.ip()) { - let resolver = direct_bind - .as_deref() - .and_then(|bind| direct::preferred_dns_server(dns_addr, bind)) - .ok_or_else(|| std::io::Error::new(ErrorKind::NotFound, "no usable physical DNS resolver"))?; - info.dst.set_ip(resolver); - } restore_bypass_destination(&mut info, virtual_dns.as_ref(), dns_addr, direct_bind.as_ref()).await?; let proxy_handler = no_proxy_mgr.new_proxy_handler(info, None, true).await?; return handle_udp_associate_session( @@ -1184,7 +1196,11 @@ where let result = Some(relay.await); if let Some(Err(err)) = result { - log::debug!("Ending {info} with \"{err}\""); + if matches!(&err, Error::Io(error) if error.kind() == std::io::ErrorKind::TimedOut) { + log::debug!("Ending idle {info}: {err}"); + } else { + log::warn!("Ending {info} with \"{err}\""); + } } }); } @@ -1650,75 +1666,92 @@ async fn handle_udp_associate_session( let udp_server = create_udp_stream(&socket_queue, udp_addr, bind.as_ref()).await?; Ok::<_, Error>((server, udp_server)) }; - let (_server, mut udp_server) = tokio::time::timeout(setup_timeout, setup) + let (_server, udp_server) = tokio::time::timeout(setup_timeout, setup) .await .map_err(|_| Error::from(format!("{session_info} UDP association setup timed out after {setup_timeout:?}")))??; - let mut buf1 = [0_u8; 4096]; - let mut buf2 = [0_u8; 4096]; - loop { - tokio::select! { - len = udp_stack.read(&mut buf1) => { - let len = len?; - if len == 0 { - break; - } - let buf1 = &buf1[..len]; + log::info!( + "UDP relay {session_info}: route={proxy_type}, local={:?}, peer={:?}, interface={:?}", + udp_server.0.local_addr().ok(), + udp_server.0.peer_addr().ok(), + bind.as_ref() + ); + let mut buf1 = vec![0_u8; u16::MAX as usize]; + let mut buf2 = vec![0_u8; u16::MAX as usize]; + let (mut sent, mut received) = (0u64, 0u64); + let result: crate::Result<()> = async { + loop { + tokio::select! { + len = udp_stack.recv(&mut buf1) => { + let Some(len) = len? else { break; }; + let buf1 = &buf1[..len]; + + crate::traffic_status::traffic_status_update(len, 0)?; + + if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { + let s5addr = if let Some(domain_name) = &domain_name { + Address::DomainAddress(domain_name.to_string().into(), session_info.dst.port()) + } else { + session_info.dst.into() + }; - crate::traffic_status::traffic_status_update(len, 0)?; + // Add SOCKS5 UDP header to the incoming data + let mut s5_udp_data = Vec::::new(); + UdpHeader::new(0, s5addr).write_to_stream(&mut s5_udp_data)?; + s5_udp_data.extend_from_slice(buf1); - if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { - let s5addr = if let Some(domain_name) = &domain_name { - Address::DomainAddress(domain_name.to_string().into(), session_info.dst.port()) + udp_server.0.send(&s5_udp_data).await?; } else { - session_info.dst.into() - }; - - // Add SOCKS5 UDP header to the incoming data - let mut s5_udp_data = Vec::::new(); - UdpHeader::new(0, s5addr).write_to_stream(&mut s5_udp_data)?; - s5_udp_data.extend_from_slice(buf1); - - udp_server.write_all(&s5_udp_data).await?; - } else { - udp_server.write_all(buf1).await?; - } - } - len = udp_server.read(&mut buf2) => { - let len = len?; - if len == 0 { - break; + udp_server.0.send(buf1).await?; + } + sent += 1; } - let buf2 = &buf2[..len]; + len = udp_server.0.recv(&mut buf2) => { + let len = len?; + let buf2 = &buf2[..len]; - crate::traffic_status::traffic_status_update(0, len)?; - - if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { - // Remove SOCKS5 UDP header from the server data - let header = UdpHeader::retrieve_from_stream(&mut &buf2[..])?; - let data = &buf2[header.len()..]; + crate::traffic_status::traffic_status_update(0, len)?; - let buf = if session_info.dst.port() == DNS_PORT { - let mut message = dns::parse_data_to_dns_message(data, false)?; - if !ipv6_enabled { - dns::remove_ipv6_entries(&mut message); + if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { + // RFC 1928 fragmentation is unsupported; a rejected packet + // must not tear down an otherwise valid association. + if buf2.len() < 4 || buf2[..3] != [0, 0, 0] { + log::debug!("{session_info}: dropped malformed or fragmented SOCKS5 UDP response"); + continue; } - message.to_vec()? - } else { - data.to_vec() - }; + // Remove SOCKS5 UDP header from the server data + let header = match UdpHeader::retrieve_from_stream(&mut &buf2[..]) { + Ok(header) => header, + Err(error) => { + log::debug!("{session_info}: dropped malformed SOCKS5 UDP response: {error}"); + continue; + } + }; + let data = &buf2[header.len()..]; - udp_stack.write_all(&buf).await?; - } else { - udp_stack.write_all(buf2).await?; + let buf = if session_info.dst.port() == DNS_PORT { + let mut message = dns::parse_data_to_dns_message(data, false)?; + if !ipv6_enabled { + dns::remove_ipv6_entries(&mut message); + } + message.to_vec()? + } else { + data.to_vec() + }; + + udp_stack.send(&buf)?; + } else { + udp_stack.send(buf2)?; + } + received += 1; } } } + Ok(()) } - - log::debug!("Ending {session_info}"); - - Ok(()) + .await; + log::info!("Ending UDP relay {session_info}: sent_datagrams={sent}, received_datagrams={received}, result={result:?}"); + result } async fn handle_dns_over_tcp_session( @@ -1861,6 +1894,51 @@ async fn handle_proxy_session(server: &mut TcpStream, proxy_handler: Arc panic!("UDP relay closed before its reply: {result:?}"), + _ = peer => {} + } + // Dropping the relay future closes its sockets and stack stream. + } + }) + .await + .unwrap(); + } +} + #[cfg(test)] mod tcp_handshake_tests { use super::*; diff --git a/src/physical_dns.rs b/src/physical_dns.rs new file mode 100644 index 00000000..02f41b3e --- /dev/null +++ b/src/physical_dns.rs @@ -0,0 +1,117 @@ +//! DNS portal shared by TCP and UDP, independent of socket-owner attribution. + +use crate::direct::{self, BindInterface}; +use std::{io, net::IpAddr, sync::Arc}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + +static QUERIES: tokio::sync::Semaphore = tokio::sync::Semaphore::const_new(crate::MAX_OUTSTANDING_DNS_QUERIES); + +async fn answer(request: &[u8], configured_dns: IpAddr, iface: &BindInterface, ipv6: bool, udp: bool) -> io::Result> { + let mut query = crate::dns::parse_data_to_dns_message(request, false).map_err(io::Error::other)?; + crate::dns::validate_dns_query(&query).map_err(io::Error::other)?; + let max_size = udp.then_some(usize::from(query.max_payload())); + let result = match QUERIES.try_acquire() { + Ok(_permit) => direct::query_bound(request, configured_dns, iface).await, + Err(_) => Err(io::Error::new(io::ErrorKind::WouldBlock, "physical DNS query capacity exhausted")), + }; + let mut response = match result { + Ok(bytes) => crate::dns::parse_data_to_dns_message(&bytes, false).map_err(io::Error::other)?, + Err(error) => { + log::warn!("Physical DNS query {} via {iface} failed: {error}", query.queries[0].name()); + query.metadata.message_type = hickory_proto::op::MessageType::Response; + query.metadata.response_code = hickory_proto::op::ResponseCode::ServFail; + query.answers.clear(); + query + } + }; + if !ipv6 { + crate::dns::remove_ipv6_entries(&mut response); + } + encode_response(&response, max_size) +} + +fn encode_response(response: &hickory_proto::op::Message, max_size: Option) -> io::Result> { + let bytes = response.to_vec().map_err(io::Error::other)?; + if max_size.is_some_and(|limit| bytes.len() > limit) { + // The upstream TCP fallback can be larger than the client's UDP limit. + // Let that client retry over TCP instead of emitting oversized DNS UDP. + response.truncate().to_vec().map_err(io::Error::other) + } else { + Ok(bytes) + } +} + +pub(crate) async fn tcp(stream: &mut S, configured_dns: IpAddr, iface: &BindInterface, ipv6: bool) -> io::Result<()> +where + S: AsyncRead + AsyncWrite + Unpin, +{ + loop { + // Read one prefix byte separately to distinguish EOF from a torn frame. + let mut prefix = [0; 2]; + if stream.read(&mut prefix[..1]).await? == 0 { + return Ok(()); + } + stream.read_exact(&mut prefix[1..]).await?; + let mut request = vec![0; u16::from_be_bytes(prefix) as usize]; + stream.read_exact(&mut request).await?; + let response = answer(&request, configured_dns, iface, ipv6, false).await?; + stream.write_u16(u16::try_from(response.len()).map_err(io::Error::other)?).await?; + stream.write_all(&response).await?; + } +} + +pub(crate) async fn udp( + mut stream: ipstack::IpStackUdpStream, + configured_dns: IpAddr, + iface: Arc, + ipv6: bool, +) -> io::Result<()> { + let mut queries = tokio::task::JoinSet::new(); + let mut buffer = vec![0; u16::MAX as usize]; + loop { + tokio::select! { + received = stream.recv(&mut buffer), if queries.len() < 16 => { + let Some(size) = received? else { return Ok(()); }; + let request = buffer[..size].to_vec(); + let iface = Arc::clone(&iface); + queries.spawn(async move { answer(&request, configured_dns, &iface, ipv6, true).await }); + } + completed = queries.join_next(), if !queries.is_empty() => { + match completed { + Some(Ok(Ok(response))) => stream.send(&response)?, + Some(Ok(Err(error))) => log::warn!("Rejected physical DNS datagram: {error}"), + Some(Err(error)) => return Err(io::Error::other(error)), + None => {} + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hickory_proto::{ + op::{Message, MessageType, OpCode, Query}, + rr::{Name, RData, Record, RecordType, rdata::A}, + }; + + #[test] + fn udp_limit_sets_truncation_without_truncating_tcp_answers() { + let name = Name::from_ascii("game.example.test.").unwrap(); + let mut response = Message::new(42, MessageType::Response, OpCode::Query); + response.add_query(Query::query(name.clone(), RecordType::A)); + for i in 1..80 { + response.add_answer(Record::from_rdata(name.clone(), 30, RData::A(A::new(192, 0, 2, i)))); + } + let tcp = encode_response(&response, None).unwrap(); + assert!(tcp.len() > 512); + let udp = encode_response(&response, Some(512)).unwrap(); + assert!(udp.len() <= 512); + let udp = Message::from_vec(&udp).unwrap(); + assert!(udp.truncation); + assert_eq!(udp.id, 42); + assert_eq!(udp.queries, response.queries); + assert_eq!(Message::from_vec(&tcp).unwrap().answers.len(), 79); + } +} diff --git a/src/process/mod.rs b/src/process/mod.rs index ff30b5c7..b5095f6f 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -45,12 +45,13 @@ pub(crate) struct ProcessMatcher { names: ProcessBypass, cache: Mutex, lookup_slot: std::sync::Arc, + exclude_self: bool, } /// The socket owner's identity is pinned for the lifetime of one relay. An OS /// table refresh can lose an owner, recycle a UDP port, or see a different /// ancestor chain; none of those events is a change to this session's policy. -pub(crate) struct SessionPolicy { +pub struct SessionPolicy { names: ProcessBypass, identities: Vec, changes: tokio::sync::watch::Receiver, @@ -58,11 +59,11 @@ pub(crate) struct SessionPolicy { } impl SessionPolicy { - pub(crate) fn bypass(&self) -> bool { + pub fn bypass(&self) -> bool { self.bypass } - pub(crate) async fn wait_for_routing_change(mut self) { + pub async fn wait_for_routing_change(mut self) { loop { if self.changes.changed().await.is_err() { std::future::pending::<()>().await; @@ -92,6 +93,7 @@ impl ProcessMatcher { names, cache: Mutex::new(Cache::default()), lookup_slot: std::sync::Arc::new(tokio::sync::Semaphore::new(1)), + exclude_self: false, }) } @@ -167,6 +169,12 @@ impl ProcessMatcher { } let mut identities = Vec::new(); for pid in pids { + // Internal SOCKS connections carry already-classified TUN traffic. + // A selected ancestor (or the mandatory self rule) must never turn + // those connections into blanket direct forwarding. + if self.exclude_self && pid == std::process::id() { + continue; + } let names = cache.pid_names.entry(pid).or_insert_with(|| { imp::process_names(pid) .into_iter() @@ -209,6 +217,30 @@ impl ProcessMatcher { } } +/// Applies user process rules to a local proxy's accepted loopback connections. +/// Remote LAN peers are never looked up in the local OS socket table. The proxy +/// process itself is excluded before ancestry matching to preserve TUN routing. +pub struct LocalProcessMatcher(std::sync::Arc); + +impl LocalProcessMatcher { + pub fn new(names: ProcessBypass) -> Self { + Self(std::sync::Arc::new(ProcessMatcher { + names, + cache: Mutex::new(Cache::default()), + lookup_slot: std::sync::Arc::new(tokio::sync::Semaphore::new(1)), + exclude_self: true, + })) + } + + /// Match the caller's tuple (peer -> listener), never its reverse. + pub async fn match_tcp(&self, peer: SocketAddr, listener: SocketAddr) -> Option { + if !self.0.names.is_configured() || !peer.ip().is_loopback() || !listener.ip().is_loopback() { + return None; + } + Some(self.0.match_session(IpProtocol::Tcp, peer, listener).await) + } +} + fn snapshot_covers_request(fetched_at: Option, requested_at: Instant) -> bool { fetched_at.is_some_and(|fetched_at| fetched_at >= requested_at) } diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index c91a586c..34a9cd04 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -39,6 +39,11 @@ pub struct VirtualDns { pub struct VirtualDnsState(Arc>); impl VirtualDnsState { + /// Restore a cached fake address without allocating or rewriting mappings. + /// An unmapped pool address is rejected instead of sent to a physical NIC. + pub async fn resolve_ip(&self, address: IpAddr) -> std::io::Result>> { + crate::resolve_virtual_domain(Some(&self.0), address).await + } pub fn new(ip_pool: IpCidr) -> Self { Self(Arc::new(Mutex::new(VirtualDns::new(ip_pool)))) } @@ -435,6 +440,10 @@ mod tests { let mut resolver = resolver.lock().await; assert_eq!(resolver.find_or_allocate_ip("PLAY.GOOGLEAPIS.COM.".to_string()).unwrap(), address); assert_eq!(resolver.resolve_ip(&address).as_deref(), Some("play.googleapis.com")); + drop(resolver); + assert_eq!(recreated.resolve_ip(address).await.unwrap().as_deref(), Some("play.googleapis.com")); + assert!(recreated.resolve_ip("198.19.0.100".parse().unwrap()).await.is_err()); + assert_eq!(recreated.resolve_ip("192.0.2.1".parse().unwrap()).await.unwrap(), None); } #[tokio::test] diff --git a/src/windows_network_config.rs b/src/windows_network_config.rs index e4d4517c..ad325879 100644 --- a/src/windows_network_config.rs +++ b/src/windows_network_config.rs @@ -748,11 +748,19 @@ fn physical_route_metric(route: &MIB_IPFORWARD_ROW2, iface: &MIB_IPINTERFACE_ROW .then_some(iface.Metric) } -pub(crate) fn physical_default_interface(tun_name: Option<&str>) -> Option { +pub(crate) fn physical_default_interface(tun_name: Option<&str>, candidates: &[u32]) -> Option { use windows_sys::Win32::NetworkManagement::IpHelper::ConvertInterfaceLuidToIndex; let mut operations = IpHelperOperations; let tun_luid = tun_name.and_then(|name| operations.interface_luid(name).ok()).unwrap_or(0); - let routes = operations.physical_routes().ok()?; + let mut routes = operations.physical_routes().ok()?; + routes.retain(|(route, _)| { + let luid = NET_LUID_LH { + Value: route.interface_luid, + }; + let mut index = 0; + // SAFETY: input LUID comes from the OS snapshot and output is writable. + (unsafe { ConvertInterfaceLuidToIndex(&luid, &mut index) }) == NO_ERROR && candidates.contains(&index) + }); let route = select_physical_route(&routes, IpAddr::V4(Ipv4Addr::UNSPECIFIED), tun_luid, &[])?; let luid = NET_LUID_LH { Value: route.interface_luid, diff --git a/tests/tests.py b/tests/tests.py index 7175f64f..a5b056ee 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,6 +1,7 @@ import glob import itertools import os +import socket import subprocess import time import unittest @@ -25,7 +26,7 @@ def get_ip(version=None): 4: 'api4.', 6: 'api6.' }[version] - result = requests.Session().get(provider % prefix).text.strip() + result = requests.Session().get(provider % prefix, timeout=15).text.strip() return result @@ -46,10 +47,21 @@ def sudo_kill_process_and_children(proc): class Tun2ProxyTest(unittest.TestCase): @staticmethod def _test(ip_version, dns, proxy_var): + proxy = os.getenv(proxy_var) + if not proxy: + raise unittest.SkipTest(f'{proxy_var} is not configured') + if ip_version == 6: + # Check route availability without sending a packet. An IPv6-only + # external service cannot exercise a runner with no IPv6 route. + try: + with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as probe: + probe.connect(('2001:4860:4860::8888', 53)) + except OSError as error: + raise unittest.SkipTest(f'runner has no IPv6 route: {error}') from error ip_noproxy = get_ip(ip_version) additional = ['-6'] if ip_version == 6 else [] p = subprocess.Popen( - ['sudo', get_tool_path(), "--proxy", os.getenv(proxy_var), '--setup', '-v', 'trace', '--dns', dns, *additional]) + ['sudo', get_tool_path(), "--proxy", proxy, '--setup', '-v', 'trace', '--dns', dns, *additional]) try: time.sleep(1) ip_withproxy = get_ip(ip_version) @@ -70,7 +82,7 @@ def add_tests(cls): for ip_version, dns, proxy_var in itertools.product(ip_options, ['virtual', 'over-tcp'], ['SOCKS5_PROXY', 'HTTP_PROXY']): setattr(cls, 'test_ipv%s_dns%s_proxy%s' % (ip_version, dns, proxy_var), - lambda self: cls._test(ip_version, dns, proxy_var)) + lambda self, version=ip_version, mode=dns, proxy=proxy_var: cls._test(version, mode, proxy)) if __name__ == '__main__':