├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── issues.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── dashboard.png ├── example-encrypted-dns.toml ├── logo.png ├── src ├── anonymized_dns.rs ├── blacklist.rs ├── cache.rs ├── config.rs ├── crypto.rs ├── dns.rs ├── dnscrypt.rs ├── dnscrypt_certs.rs ├── errors.rs ├── globals.rs ├── main.rs ├── metrics.rs ├── resolver.rs └── varz.rs └── undelegated.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: dnscrypt 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v9 14 | with: 15 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 16 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-20.04, windows-latest] 14 | include: 15 | - os: ubuntu-20.04 16 | target: x86_64-unknown-linux-gnu 17 | target_alias: linux-x86_64 18 | bin_suffix: '' 19 | archive_suffix: '.tar.bz2' 20 | - os: windows-latest 21 | target: x86_64-pc-windows-msvc 22 | target_alias: win-x86_64-msvc 23 | bin_suffix: '.exe' 24 | archive_suffix: '.zip' 25 | defaults: 26 | run: 27 | shell: bash 28 | env: 29 | ARCHIVE_PATH: encrypted-dns_${{ github.ref_name }}_${{ matrix.target_alias }}${{ matrix.archive_suffix }} 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - uses: mlugg/setup-zig@v1 35 | 36 | - uses: hecrj/setup-rust-action@v2 37 | with: 38 | rust-version: stable 39 | targets: ${{ matrix.target }} 40 | 41 | - name: Check Cargo availability 42 | run: cargo --version 43 | 44 | - name: Check Rustup default toolchain 45 | run: rustup default | grep stable 46 | 47 | - name: Build 48 | run: | 49 | echo 'lto = "fat"' >> Cargo.toml 50 | env RUSTFLAGS="-C strip=symbols" cargo build --release 51 | mkdir encrypted-dns 52 | cp target/release/encrypted-dns${{ matrix.bin_suffix }} encrypted-dns/ 53 | cp README.md example-encrypted-dns.toml encrypted-dns/ 54 | if [ "${{ matrix.os }}" = "ubuntu-20.04" ]; then 55 | tar cjpf ${ARCHIVE_PATH} encrypted-dns 56 | elif [ "${{ matrix.os }}" = "windows-latest" ]; then 57 | "/C/Program Files/7-Zip/7z" a ${ARCHIVE_PATH} encrypted-dns 58 | fi 59 | 60 | - name: Install cargo-deb and build Debian package 61 | if: ${{ matrix.os == 'ubuntu-20.04' }} 62 | run: | 63 | cargo install cargo-deb 64 | cargo deb --output=encrypted-dns_${{ github.ref_name }}_amd64.deb --no-build 65 | 66 | - uses: actions/upload-artifact@v4 67 | with: 68 | name: encrypted-dns_${{ matrix.target_alias }} 69 | path: ${{ env.ARCHIVE_PATH }} 70 | 71 | - uses: actions/upload-artifact@v4 72 | if: ${{ matrix.os == 'ubuntu-20.04' }} 73 | with: 74 | name: encrypted-dns_deb-amd64 75 | path: encrypted-dns_${{ github.ref_name }}_amd64.deb 76 | 77 | release: 78 | if: startsWith(github.ref, 'refs/tags/') 79 | needs: 80 | - build 81 | runs-on: ubuntu-20.04 82 | 83 | steps: 84 | - uses: actions/download-artifact@v4 85 | 86 | - name: Create release 87 | uses: softprops/action-gh-release@v2 88 | env: 89 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 90 | with: 91 | name: Release ${{ github.ref_name }} 92 | draft: true 93 | prerelease: false 94 | files: | 95 | encrypted-dns_deb-amd64/*.deb 96 | encrypted-dns_linux-x86_64/*.tar.bz2 97 | encrypted-dns_win-x86_64-msvc/*.zip 98 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: hecrj/setup-rust-action@v2 15 | with: 16 | rust-version: nightly 17 | - name: Check Cargo availability 18 | run: cargo --version 19 | - name: Check Rustup default toolchain 20 | run: rustup default | grep nightly 21 | - name: Test 22 | run: | 23 | cargo test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | *~ 3 | /target/ 4 | encrypted-dns.state 5 | encrypted-dns.toml 6 | a.rb 7 | sizes.txt 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "adler32" 22 | version = "1.2.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 25 | 26 | [[package]] 27 | name = "ahash" 28 | version = "0.8.12" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 31 | dependencies = [ 32 | "cfg-if", 33 | "once_cell", 34 | "version_check", 35 | "zerocopy", 36 | ] 37 | 38 | [[package]] 39 | name = "allocator-api2" 40 | version = "0.2.21" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anyhow" 52 | version = "1.0.98" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 55 | 56 | [[package]] 57 | name = "arbitrary" 58 | version = "1.4.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 61 | dependencies = [ 62 | "derive_arbitrary", 63 | ] 64 | 65 | [[package]] 66 | name = "autocfg" 67 | version = "1.4.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 70 | 71 | [[package]] 72 | name = "backtrace" 73 | version = "0.3.75" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 76 | dependencies = [ 77 | "addr2line", 78 | "cfg-if", 79 | "libc", 80 | "miniz_oxide", 81 | "object", 82 | "rustc-demangle", 83 | "windows-targets", 84 | ] 85 | 86 | [[package]] 87 | name = "base64" 88 | version = "0.22.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 91 | 92 | [[package]] 93 | name = "bitflags" 94 | version = "2.9.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 97 | 98 | [[package]] 99 | name = "bumpalo" 100 | version = "3.17.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 103 | 104 | [[package]] 105 | name = "byteorder" 106 | version = "1.5.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 109 | 110 | [[package]] 111 | name = "bytes" 112 | version = "1.10.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 115 | 116 | [[package]] 117 | name = "cc" 118 | version = "1.2.23" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" 121 | dependencies = [ 122 | "shlex", 123 | ] 124 | 125 | [[package]] 126 | name = "cfg-if" 127 | version = "1.0.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 130 | 131 | [[package]] 132 | name = "cfg_aliases" 133 | version = "0.1.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "4.5.38" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" 142 | dependencies = [ 143 | "clap_builder", 144 | ] 145 | 146 | [[package]] 147 | name = "clap_builder" 148 | version = "4.5.38" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" 151 | dependencies = [ 152 | "anstyle", 153 | "clap_lex", 154 | "terminal_size", 155 | ] 156 | 157 | [[package]] 158 | name = "clap_lex" 159 | version = "0.7.4" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 162 | 163 | [[package]] 164 | name = "coarsetime" 165 | version = "0.1.36" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" 168 | dependencies = [ 169 | "libc", 170 | "wasix", 171 | "wasm-bindgen", 172 | ] 173 | 174 | [[package]] 175 | name = "core2" 176 | version = "0.4.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 179 | dependencies = [ 180 | "memchr", 181 | ] 182 | 183 | [[package]] 184 | name = "crc32fast" 185 | version = "1.4.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 188 | dependencies = [ 189 | "cfg-if", 190 | ] 191 | 192 | [[package]] 193 | name = "crossbeam-utils" 194 | version = "0.8.21" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 197 | 198 | [[package]] 199 | name = "ct-codecs" 200 | version = "1.1.6" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" 203 | 204 | [[package]] 205 | name = "daemonize-simple" 206 | version = "0.1.6" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "4094aa1ec57f4c8f0b1d8417bf7a71f6efb2470bcfdfc70639f2147f525bf86c" 209 | dependencies = [ 210 | "libc", 211 | ] 212 | 213 | [[package]] 214 | name = "dary_heap" 215 | version = "0.3.7" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" 218 | 219 | [[package]] 220 | name = "derive_arbitrary" 221 | version = "1.4.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" 224 | dependencies = [ 225 | "proc-macro2", 226 | "quote", 227 | "syn", 228 | ] 229 | 230 | [[package]] 231 | name = "displaydoc" 232 | version = "0.2.5" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 235 | dependencies = [ 236 | "proc-macro2", 237 | "quote", 238 | "syn", 239 | ] 240 | 241 | [[package]] 242 | name = "dnsstamps" 243 | version = "0.1.10" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "fa047b0ecdc7201c8009c4558f6182efdb07cbe011e5b1dc964d358e25ab7594" 246 | dependencies = [ 247 | "byteorder", 248 | "ct-codecs", 249 | ] 250 | 251 | [[package]] 252 | name = "educe" 253 | version = "0.6.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" 256 | dependencies = [ 257 | "enum-ordinalize", 258 | "proc-macro2", 259 | "quote", 260 | "syn", 261 | ] 262 | 263 | [[package]] 264 | name = "encrypted-dns" 265 | version = "0.9.17" 266 | dependencies = [ 267 | "anyhow", 268 | "byteorder", 269 | "clap", 270 | "coarsetime", 271 | "daemonize-simple", 272 | "dnsstamps", 273 | "educe", 274 | "env_logger", 275 | "futures", 276 | "http-body-util", 277 | "hyper", 278 | "hyper-util", 279 | "ipext", 280 | "libsodium-sys-stable", 281 | "log", 282 | "mimalloc", 283 | "parking_lot", 284 | "privdrop", 285 | "prometheus", 286 | "rand", 287 | "rlimit", 288 | "rustc-hash", 289 | "serde", 290 | "serde-big-array", 291 | "serde_derive", 292 | "sieve-cache", 293 | "siphasher", 294 | "slabigator", 295 | "socket2", 296 | "tokio", 297 | "toml", 298 | ] 299 | 300 | [[package]] 301 | name = "enum-ordinalize" 302 | version = "4.3.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" 305 | dependencies = [ 306 | "enum-ordinalize-derive", 307 | ] 308 | 309 | [[package]] 310 | name = "enum-ordinalize-derive" 311 | version = "4.3.1" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "syn", 318 | ] 319 | 320 | [[package]] 321 | name = "env_filter" 322 | version = "0.1.3" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 325 | dependencies = [ 326 | "log", 327 | ] 328 | 329 | [[package]] 330 | name = "env_logger" 331 | version = "0.11.8" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 334 | dependencies = [ 335 | "env_filter", 336 | "jiff", 337 | "log", 338 | ] 339 | 340 | [[package]] 341 | name = "equivalent" 342 | version = "1.0.2" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 345 | 346 | [[package]] 347 | name = "errno" 348 | version = "0.3.12" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 351 | dependencies = [ 352 | "libc", 353 | "windows-sys 0.59.0", 354 | ] 355 | 356 | [[package]] 357 | name = "filetime" 358 | version = "0.2.25" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 361 | dependencies = [ 362 | "cfg-if", 363 | "libc", 364 | "libredox", 365 | "windows-sys 0.59.0", 366 | ] 367 | 368 | [[package]] 369 | name = "flate2" 370 | version = "1.1.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" 373 | dependencies = [ 374 | "crc32fast", 375 | "miniz_oxide", 376 | ] 377 | 378 | [[package]] 379 | name = "fnv" 380 | version = "1.0.7" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 383 | 384 | [[package]] 385 | name = "form_urlencoded" 386 | version = "1.2.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 389 | dependencies = [ 390 | "percent-encoding", 391 | ] 392 | 393 | [[package]] 394 | name = "futures" 395 | version = "0.3.31" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 398 | dependencies = [ 399 | "futures-channel", 400 | "futures-core", 401 | "futures-executor", 402 | "futures-io", 403 | "futures-sink", 404 | "futures-task", 405 | "futures-util", 406 | ] 407 | 408 | [[package]] 409 | name = "futures-channel" 410 | version = "0.3.31" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 413 | dependencies = [ 414 | "futures-core", 415 | "futures-sink", 416 | ] 417 | 418 | [[package]] 419 | name = "futures-core" 420 | version = "0.3.31" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 423 | 424 | [[package]] 425 | name = "futures-executor" 426 | version = "0.3.31" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 429 | dependencies = [ 430 | "futures-core", 431 | "futures-task", 432 | "futures-util", 433 | ] 434 | 435 | [[package]] 436 | name = "futures-io" 437 | version = "0.3.31" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 440 | 441 | [[package]] 442 | name = "futures-macro" 443 | version = "0.3.31" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 446 | dependencies = [ 447 | "proc-macro2", 448 | "quote", 449 | "syn", 450 | ] 451 | 452 | [[package]] 453 | name = "futures-sink" 454 | version = "0.3.31" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 457 | 458 | [[package]] 459 | name = "futures-task" 460 | version = "0.3.31" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 463 | 464 | [[package]] 465 | name = "futures-util" 466 | version = "0.3.31" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 469 | dependencies = [ 470 | "futures-channel", 471 | "futures-core", 472 | "futures-io", 473 | "futures-macro", 474 | "futures-sink", 475 | "futures-task", 476 | "memchr", 477 | "pin-project-lite", 478 | "pin-utils", 479 | "slab", 480 | ] 481 | 482 | [[package]] 483 | name = "getrandom" 484 | version = "0.3.3" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 487 | dependencies = [ 488 | "cfg-if", 489 | "libc", 490 | "r-efi", 491 | "wasi 0.14.2+wasi-0.2.4", 492 | ] 493 | 494 | [[package]] 495 | name = "gimli" 496 | version = "0.31.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 499 | 500 | [[package]] 501 | name = "hashbrown" 502 | version = "0.14.5" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 505 | dependencies = [ 506 | "ahash", 507 | "allocator-api2", 508 | ] 509 | 510 | [[package]] 511 | name = "hashbrown" 512 | version = "0.15.3" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 515 | 516 | [[package]] 517 | name = "hex" 518 | version = "0.4.3" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 521 | 522 | [[package]] 523 | name = "http" 524 | version = "1.3.1" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 527 | dependencies = [ 528 | "bytes", 529 | "fnv", 530 | "itoa", 531 | ] 532 | 533 | [[package]] 534 | name = "http-body" 535 | version = "1.0.1" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 538 | dependencies = [ 539 | "bytes", 540 | "http", 541 | ] 542 | 543 | [[package]] 544 | name = "http-body-util" 545 | version = "0.1.3" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 548 | dependencies = [ 549 | "bytes", 550 | "futures-core", 551 | "http", 552 | "http-body", 553 | "pin-project-lite", 554 | ] 555 | 556 | [[package]] 557 | name = "httparse" 558 | version = "1.10.1" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 561 | 562 | [[package]] 563 | name = "httpdate" 564 | version = "1.0.3" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 567 | 568 | [[package]] 569 | name = "hyper" 570 | version = "1.6.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 573 | dependencies = [ 574 | "bytes", 575 | "futures-channel", 576 | "futures-util", 577 | "http", 578 | "http-body", 579 | "httparse", 580 | "httpdate", 581 | "itoa", 582 | "pin-project-lite", 583 | "smallvec", 584 | "tokio", 585 | ] 586 | 587 | [[package]] 588 | name = "hyper-util" 589 | version = "0.1.12" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" 592 | dependencies = [ 593 | "bytes", 594 | "futures-util", 595 | "http", 596 | "http-body", 597 | "hyper", 598 | "pin-project-lite", 599 | "tokio", 600 | ] 601 | 602 | [[package]] 603 | name = "icu_collections" 604 | version = "2.0.0" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 607 | dependencies = [ 608 | "displaydoc", 609 | "potential_utf", 610 | "yoke", 611 | "zerofrom", 612 | "zerovec", 613 | ] 614 | 615 | [[package]] 616 | name = "icu_locale_core" 617 | version = "2.0.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 620 | dependencies = [ 621 | "displaydoc", 622 | "litemap", 623 | "tinystr", 624 | "writeable", 625 | "zerovec", 626 | ] 627 | 628 | [[package]] 629 | name = "icu_normalizer" 630 | version = "2.0.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 633 | dependencies = [ 634 | "displaydoc", 635 | "icu_collections", 636 | "icu_normalizer_data", 637 | "icu_properties", 638 | "icu_provider", 639 | "smallvec", 640 | "zerovec", 641 | ] 642 | 643 | [[package]] 644 | name = "icu_normalizer_data" 645 | version = "2.0.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 648 | 649 | [[package]] 650 | name = "icu_properties" 651 | version = "2.0.1" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 654 | dependencies = [ 655 | "displaydoc", 656 | "icu_collections", 657 | "icu_locale_core", 658 | "icu_properties_data", 659 | "icu_provider", 660 | "potential_utf", 661 | "zerotrie", 662 | "zerovec", 663 | ] 664 | 665 | [[package]] 666 | name = "icu_properties_data" 667 | version = "2.0.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 670 | 671 | [[package]] 672 | name = "icu_provider" 673 | version = "2.0.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 676 | dependencies = [ 677 | "displaydoc", 678 | "icu_locale_core", 679 | "stable_deref_trait", 680 | "tinystr", 681 | "writeable", 682 | "yoke", 683 | "zerofrom", 684 | "zerotrie", 685 | "zerovec", 686 | ] 687 | 688 | [[package]] 689 | name = "idna" 690 | version = "1.0.3" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 693 | dependencies = [ 694 | "idna_adapter", 695 | "smallvec", 696 | "utf8_iter", 697 | ] 698 | 699 | [[package]] 700 | name = "idna_adapter" 701 | version = "1.2.1" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 704 | dependencies = [ 705 | "icu_normalizer", 706 | "icu_properties", 707 | ] 708 | 709 | [[package]] 710 | name = "indexmap" 711 | version = "2.9.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 714 | dependencies = [ 715 | "equivalent", 716 | "hashbrown 0.15.3", 717 | ] 718 | 719 | [[package]] 720 | name = "ipext" 721 | version = "0.1.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "285023c09f39035e8164633e24253f0568c5a8cb5705dd19065af702389522a8" 724 | 725 | [[package]] 726 | name = "itoa" 727 | version = "1.0.15" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 730 | 731 | [[package]] 732 | name = "jiff" 733 | version = "0.2.14" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" 736 | dependencies = [ 737 | "jiff-static", 738 | "log", 739 | "portable-atomic", 740 | "portable-atomic-util", 741 | "serde", 742 | ] 743 | 744 | [[package]] 745 | name = "jiff-static" 746 | version = "0.2.14" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" 749 | dependencies = [ 750 | "proc-macro2", 751 | "quote", 752 | "syn", 753 | ] 754 | 755 | [[package]] 756 | name = "lazy_static" 757 | version = "1.5.0" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 760 | 761 | [[package]] 762 | name = "libc" 763 | version = "0.2.172" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 766 | 767 | [[package]] 768 | name = "libflate" 769 | version = "2.1.0" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" 772 | dependencies = [ 773 | "adler32", 774 | "core2", 775 | "crc32fast", 776 | "dary_heap", 777 | "libflate_lz77", 778 | ] 779 | 780 | [[package]] 781 | name = "libflate_lz77" 782 | version = "2.1.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" 785 | dependencies = [ 786 | "core2", 787 | "hashbrown 0.14.5", 788 | "rle-decode-fast", 789 | ] 790 | 791 | [[package]] 792 | name = "libmimalloc-sys" 793 | version = "0.1.42" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" 796 | dependencies = [ 797 | "cc", 798 | "libc", 799 | ] 800 | 801 | [[package]] 802 | name = "libredox" 803 | version = "0.1.3" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 806 | dependencies = [ 807 | "bitflags", 808 | "libc", 809 | "redox_syscall", 810 | ] 811 | 812 | [[package]] 813 | name = "libsodium-sys-stable" 814 | version = "1.22.3" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "b023d38f2afdfe36f81e15a9d7232097701d7b107e3a93ba903083985e235239" 817 | dependencies = [ 818 | "cc", 819 | "libc", 820 | "libflate", 821 | "minisign-verify", 822 | "pkg-config", 823 | "tar", 824 | "ureq", 825 | "vcpkg", 826 | "zip", 827 | ] 828 | 829 | [[package]] 830 | name = "linux-raw-sys" 831 | version = "0.4.15" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 834 | 835 | [[package]] 836 | name = "linux-raw-sys" 837 | version = "0.9.4" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 840 | 841 | [[package]] 842 | name = "litemap" 843 | version = "0.8.0" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 846 | 847 | [[package]] 848 | name = "lock_api" 849 | version = "0.4.12" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 852 | dependencies = [ 853 | "autocfg", 854 | "scopeguard", 855 | ] 856 | 857 | [[package]] 858 | name = "log" 859 | version = "0.4.27" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 862 | 863 | [[package]] 864 | name = "memchr" 865 | version = "2.7.4" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 868 | 869 | [[package]] 870 | name = "mimalloc" 871 | version = "0.1.46" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" 874 | dependencies = [ 875 | "libmimalloc-sys", 876 | ] 877 | 878 | [[package]] 879 | name = "minisign-verify" 880 | version = "0.2.3" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3" 883 | 884 | [[package]] 885 | name = "miniz_oxide" 886 | version = "0.8.8" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 889 | dependencies = [ 890 | "adler2", 891 | ] 892 | 893 | [[package]] 894 | name = "mio" 895 | version = "1.0.3" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 898 | dependencies = [ 899 | "libc", 900 | "wasi 0.11.0+wasi-snapshot-preview1", 901 | "windows-sys 0.52.0", 902 | ] 903 | 904 | [[package]] 905 | name = "nix" 906 | version = "0.28.0" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" 909 | dependencies = [ 910 | "bitflags", 911 | "cfg-if", 912 | "cfg_aliases", 913 | "libc", 914 | ] 915 | 916 | [[package]] 917 | name = "object" 918 | version = "0.36.7" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 921 | dependencies = [ 922 | "memchr", 923 | ] 924 | 925 | [[package]] 926 | name = "once_cell" 927 | version = "1.21.3" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 930 | 931 | [[package]] 932 | name = "parking_lot" 933 | version = "0.12.3" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 936 | dependencies = [ 937 | "lock_api", 938 | "parking_lot_core", 939 | ] 940 | 941 | [[package]] 942 | name = "parking_lot_core" 943 | version = "0.9.10" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 946 | dependencies = [ 947 | "cfg-if", 948 | "libc", 949 | "redox_syscall", 950 | "smallvec", 951 | "windows-targets", 952 | ] 953 | 954 | [[package]] 955 | name = "percent-encoding" 956 | version = "2.3.1" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 959 | 960 | [[package]] 961 | name = "pin-project-lite" 962 | version = "0.2.16" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 965 | 966 | [[package]] 967 | name = "pin-utils" 968 | version = "0.1.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 971 | 972 | [[package]] 973 | name = "pkg-config" 974 | version = "0.3.32" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 977 | 978 | [[package]] 979 | name = "portable-atomic" 980 | version = "1.11.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 983 | 984 | [[package]] 985 | name = "portable-atomic-util" 986 | version = "0.2.4" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 989 | dependencies = [ 990 | "portable-atomic", 991 | ] 992 | 993 | [[package]] 994 | name = "potential_utf" 995 | version = "0.1.2" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 998 | dependencies = [ 999 | "zerovec", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "ppv-lite86" 1004 | version = "0.2.21" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1007 | dependencies = [ 1008 | "zerocopy", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "privdrop" 1013 | version = "0.5.5" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "879d008129b086c1c067a3b7ce406bb9766c29f20387e7883724d8dddbce7064" 1016 | dependencies = [ 1017 | "libc", 1018 | "nix", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "proc-macro2" 1023 | version = "1.0.95" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1026 | dependencies = [ 1027 | "unicode-ident", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "procfs" 1032 | version = "0.16.0" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" 1035 | dependencies = [ 1036 | "bitflags", 1037 | "hex", 1038 | "lazy_static", 1039 | "procfs-core", 1040 | "rustix 0.38.44", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "procfs-core" 1045 | version = "0.16.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" 1048 | dependencies = [ 1049 | "bitflags", 1050 | "hex", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "prometheus" 1055 | version = "0.13.4" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" 1058 | dependencies = [ 1059 | "cfg-if", 1060 | "fnv", 1061 | "lazy_static", 1062 | "libc", 1063 | "memchr", 1064 | "parking_lot", 1065 | "procfs", 1066 | "thiserror 1.0.69", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "quote" 1071 | version = "1.0.40" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1074 | dependencies = [ 1075 | "proc-macro2", 1076 | ] 1077 | 1078 | [[package]] 1079 | name = "r-efi" 1080 | version = "5.2.0" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1083 | 1084 | [[package]] 1085 | name = "rand" 1086 | version = "0.9.1" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 1089 | dependencies = [ 1090 | "rand_chacha", 1091 | "rand_core", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "rand_chacha" 1096 | version = "0.9.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1099 | dependencies = [ 1100 | "ppv-lite86", 1101 | "rand_core", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "rand_core" 1106 | version = "0.9.3" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1109 | dependencies = [ 1110 | "getrandom", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "redox_syscall" 1115 | version = "0.5.12" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 1118 | dependencies = [ 1119 | "bitflags", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "rle-decode-fast" 1124 | version = "1.0.3" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 1127 | 1128 | [[package]] 1129 | name = "rlimit" 1130 | version = "0.10.2" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" 1133 | dependencies = [ 1134 | "libc", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "rustc-demangle" 1139 | version = "0.1.24" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1142 | 1143 | [[package]] 1144 | name = "rustc-hash" 1145 | version = "2.1.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1148 | 1149 | [[package]] 1150 | name = "rustix" 1151 | version = "0.38.44" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1154 | dependencies = [ 1155 | "bitflags", 1156 | "errno", 1157 | "libc", 1158 | "linux-raw-sys 0.4.15", 1159 | "windows-sys 0.59.0", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "rustix" 1164 | version = "1.0.7" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 1167 | dependencies = [ 1168 | "bitflags", 1169 | "errno", 1170 | "libc", 1171 | "linux-raw-sys 0.9.4", 1172 | "windows-sys 0.59.0", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "rustversion" 1177 | version = "1.0.20" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1180 | 1181 | [[package]] 1182 | name = "scopeguard" 1183 | version = "1.2.0" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1186 | 1187 | [[package]] 1188 | name = "serde" 1189 | version = "1.0.219" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1192 | dependencies = [ 1193 | "serde_derive", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "serde-big-array" 1198 | version = "0.5.1" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" 1201 | dependencies = [ 1202 | "serde", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "serde_derive" 1207 | version = "1.0.219" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1210 | dependencies = [ 1211 | "proc-macro2", 1212 | "quote", 1213 | "syn", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "serde_spanned" 1218 | version = "0.6.8" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1221 | dependencies = [ 1222 | "serde", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "shlex" 1227 | version = "1.3.0" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1230 | 1231 | [[package]] 1232 | name = "sieve-cache" 1233 | version = "1.1.5" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "5cfaac82727ec6de7f6065963e1d4c6f539bc4d01c1df332c3e16aa2a9014f62" 1236 | 1237 | [[package]] 1238 | name = "simd-adler32" 1239 | version = "0.3.7" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1242 | 1243 | [[package]] 1244 | name = "siphasher" 1245 | version = "1.0.1" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 1248 | 1249 | [[package]] 1250 | name = "slab" 1251 | version = "0.4.9" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1254 | dependencies = [ 1255 | "autocfg", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "slabigator" 1260 | version = "0.9.5" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "420cf8ae97042519eb6eceb15ab3a454c216dcd7655d4e14522488928793821f" 1263 | 1264 | [[package]] 1265 | name = "smallvec" 1266 | version = "1.15.0" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1269 | 1270 | [[package]] 1271 | name = "socket2" 1272 | version = "0.5.9" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 1275 | dependencies = [ 1276 | "libc", 1277 | "windows-sys 0.52.0", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "stable_deref_trait" 1282 | version = "1.2.0" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1285 | 1286 | [[package]] 1287 | name = "syn" 1288 | version = "2.0.101" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1291 | dependencies = [ 1292 | "proc-macro2", 1293 | "quote", 1294 | "unicode-ident", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "synstructure" 1299 | version = "0.13.2" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1302 | dependencies = [ 1303 | "proc-macro2", 1304 | "quote", 1305 | "syn", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "tar" 1310 | version = "0.4.44" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" 1313 | dependencies = [ 1314 | "filetime", 1315 | "libc", 1316 | "xattr", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "terminal_size" 1321 | version = "0.4.2" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" 1324 | dependencies = [ 1325 | "rustix 1.0.7", 1326 | "windows-sys 0.59.0", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "thiserror" 1331 | version = "1.0.69" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1334 | dependencies = [ 1335 | "thiserror-impl 1.0.69", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "thiserror" 1340 | version = "2.0.12" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1343 | dependencies = [ 1344 | "thiserror-impl 2.0.12", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "thiserror-impl" 1349 | version = "1.0.69" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1352 | dependencies = [ 1353 | "proc-macro2", 1354 | "quote", 1355 | "syn", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "thiserror-impl" 1360 | version = "2.0.12" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1363 | dependencies = [ 1364 | "proc-macro2", 1365 | "quote", 1366 | "syn", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "tinystr" 1371 | version = "0.8.1" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1374 | dependencies = [ 1375 | "displaydoc", 1376 | "zerovec", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "tokio" 1381 | version = "1.45.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" 1384 | dependencies = [ 1385 | "backtrace", 1386 | "bytes", 1387 | "libc", 1388 | "mio", 1389 | "pin-project-lite", 1390 | "socket2", 1391 | "windows-sys 0.52.0", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "toml" 1396 | version = "0.8.22" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" 1399 | dependencies = [ 1400 | "serde", 1401 | "serde_spanned", 1402 | "toml_datetime", 1403 | "toml_edit", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "toml_datetime" 1408 | version = "0.6.9" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 1411 | dependencies = [ 1412 | "serde", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "toml_edit" 1417 | version = "0.22.26" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" 1420 | dependencies = [ 1421 | "indexmap", 1422 | "serde", 1423 | "serde_spanned", 1424 | "toml_datetime", 1425 | "toml_write", 1426 | "winnow", 1427 | ] 1428 | 1429 | [[package]] 1430 | name = "toml_write" 1431 | version = "0.1.1" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" 1434 | 1435 | [[package]] 1436 | name = "unicode-ident" 1437 | version = "1.0.18" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1440 | 1441 | [[package]] 1442 | name = "ureq" 1443 | version = "2.12.1" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" 1446 | dependencies = [ 1447 | "base64", 1448 | "log", 1449 | "once_cell", 1450 | "url", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "url" 1455 | version = "2.5.4" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1458 | dependencies = [ 1459 | "form_urlencoded", 1460 | "idna", 1461 | "percent-encoding", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "utf8_iter" 1466 | version = "1.0.4" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1469 | 1470 | [[package]] 1471 | name = "vcpkg" 1472 | version = "0.2.15" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1475 | 1476 | [[package]] 1477 | name = "version_check" 1478 | version = "0.9.5" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1481 | 1482 | [[package]] 1483 | name = "wasi" 1484 | version = "0.11.0+wasi-snapshot-preview1" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1487 | 1488 | [[package]] 1489 | name = "wasi" 1490 | version = "0.14.2+wasi-0.2.4" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1493 | dependencies = [ 1494 | "wit-bindgen-rt", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "wasix" 1499 | version = "0.12.21" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" 1502 | dependencies = [ 1503 | "wasi 0.11.0+wasi-snapshot-preview1", 1504 | ] 1505 | 1506 | [[package]] 1507 | name = "wasm-bindgen" 1508 | version = "0.2.100" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1511 | dependencies = [ 1512 | "cfg-if", 1513 | "once_cell", 1514 | "rustversion", 1515 | "wasm-bindgen-macro", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "wasm-bindgen-backend" 1520 | version = "0.2.100" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1523 | dependencies = [ 1524 | "bumpalo", 1525 | "log", 1526 | "proc-macro2", 1527 | "quote", 1528 | "syn", 1529 | "wasm-bindgen-shared", 1530 | ] 1531 | 1532 | [[package]] 1533 | name = "wasm-bindgen-macro" 1534 | version = "0.2.100" 1535 | source = "registry+https://github.com/rust-lang/crates.io-index" 1536 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1537 | dependencies = [ 1538 | "quote", 1539 | "wasm-bindgen-macro-support", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "wasm-bindgen-macro-support" 1544 | version = "0.2.100" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1547 | dependencies = [ 1548 | "proc-macro2", 1549 | "quote", 1550 | "syn", 1551 | "wasm-bindgen-backend", 1552 | "wasm-bindgen-shared", 1553 | ] 1554 | 1555 | [[package]] 1556 | name = "wasm-bindgen-shared" 1557 | version = "0.2.100" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1560 | dependencies = [ 1561 | "unicode-ident", 1562 | ] 1563 | 1564 | [[package]] 1565 | name = "windows-sys" 1566 | version = "0.52.0" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1569 | dependencies = [ 1570 | "windows-targets", 1571 | ] 1572 | 1573 | [[package]] 1574 | name = "windows-sys" 1575 | version = "0.59.0" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1578 | dependencies = [ 1579 | "windows-targets", 1580 | ] 1581 | 1582 | [[package]] 1583 | name = "windows-targets" 1584 | version = "0.52.6" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1587 | dependencies = [ 1588 | "windows_aarch64_gnullvm", 1589 | "windows_aarch64_msvc", 1590 | "windows_i686_gnu", 1591 | "windows_i686_gnullvm", 1592 | "windows_i686_msvc", 1593 | "windows_x86_64_gnu", 1594 | "windows_x86_64_gnullvm", 1595 | "windows_x86_64_msvc", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "windows_aarch64_gnullvm" 1600 | version = "0.52.6" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1603 | 1604 | [[package]] 1605 | name = "windows_aarch64_msvc" 1606 | version = "0.52.6" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1609 | 1610 | [[package]] 1611 | name = "windows_i686_gnu" 1612 | version = "0.52.6" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1615 | 1616 | [[package]] 1617 | name = "windows_i686_gnullvm" 1618 | version = "0.52.6" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1621 | 1622 | [[package]] 1623 | name = "windows_i686_msvc" 1624 | version = "0.52.6" 1625 | source = "registry+https://github.com/rust-lang/crates.io-index" 1626 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1627 | 1628 | [[package]] 1629 | name = "windows_x86_64_gnu" 1630 | version = "0.52.6" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1633 | 1634 | [[package]] 1635 | name = "windows_x86_64_gnullvm" 1636 | version = "0.52.6" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1639 | 1640 | [[package]] 1641 | name = "windows_x86_64_msvc" 1642 | version = "0.52.6" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1645 | 1646 | [[package]] 1647 | name = "winnow" 1648 | version = "0.7.10" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" 1651 | dependencies = [ 1652 | "memchr", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "wit-bindgen-rt" 1657 | version = "0.39.0" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1660 | dependencies = [ 1661 | "bitflags", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "writeable" 1666 | version = "0.6.1" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1669 | 1670 | [[package]] 1671 | name = "xattr" 1672 | version = "1.5.0" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" 1675 | dependencies = [ 1676 | "libc", 1677 | "rustix 1.0.7", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "yoke" 1682 | version = "0.8.0" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 1685 | dependencies = [ 1686 | "serde", 1687 | "stable_deref_trait", 1688 | "yoke-derive", 1689 | "zerofrom", 1690 | ] 1691 | 1692 | [[package]] 1693 | name = "yoke-derive" 1694 | version = "0.8.0" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 1697 | dependencies = [ 1698 | "proc-macro2", 1699 | "quote", 1700 | "syn", 1701 | "synstructure", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "zerocopy" 1706 | version = "0.8.25" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 1709 | dependencies = [ 1710 | "zerocopy-derive", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "zerocopy-derive" 1715 | version = "0.8.25" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 1718 | dependencies = [ 1719 | "proc-macro2", 1720 | "quote", 1721 | "syn", 1722 | ] 1723 | 1724 | [[package]] 1725 | name = "zerofrom" 1726 | version = "0.1.6" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1729 | dependencies = [ 1730 | "zerofrom-derive", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "zerofrom-derive" 1735 | version = "0.1.6" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 1738 | dependencies = [ 1739 | "proc-macro2", 1740 | "quote", 1741 | "syn", 1742 | "synstructure", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "zerotrie" 1747 | version = "0.2.2" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 1750 | dependencies = [ 1751 | "displaydoc", 1752 | "yoke", 1753 | "zerofrom", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "zerovec" 1758 | version = "0.11.2" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 1761 | dependencies = [ 1762 | "yoke", 1763 | "zerofrom", 1764 | "zerovec-derive", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "zerovec-derive" 1769 | version = "0.11.1" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 1772 | dependencies = [ 1773 | "proc-macro2", 1774 | "quote", 1775 | "syn", 1776 | ] 1777 | 1778 | [[package]] 1779 | name = "zip" 1780 | version = "2.4.2" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" 1783 | dependencies = [ 1784 | "arbitrary", 1785 | "crc32fast", 1786 | "crossbeam-utils", 1787 | "displaydoc", 1788 | "flate2", 1789 | "indexmap", 1790 | "memchr", 1791 | "thiserror 2.0.12", 1792 | "zopfli", 1793 | ] 1794 | 1795 | [[package]] 1796 | name = "zopfli" 1797 | version = "0.8.2" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" 1800 | dependencies = [ 1801 | "bumpalo", 1802 | "crc32fast", 1803 | "log", 1804 | "simd-adler32", 1805 | ] 1806 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "encrypted-dns" 3 | version = "0.9.17" 4 | authors = ["Frank Denis "] 5 | edition = "2018" 6 | description = "A modern encrypted DNS server (DNSCrypt v2, Anonymized DNSCrypt, DoH)" 7 | keywords = ["dnscrypt", "encryption", "dns", "doh", "proxy"] 8 | license = "MIT" 9 | homepage = "https://github.com/jedisct1/encrypted-dns-server" 10 | repository = "https://github.com/jedisct1/encrypted-dns-server" 11 | categories = ["asynchronous", "network-programming", "command-line-utilities"] 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | anyhow = "1.0.98" 16 | byteorder = "1.5.0" 17 | clap = { version = "4.5.38", default-features = false, features = [ 18 | "std", 19 | "cargo", 20 | "wrap_help", 21 | ] } 22 | coarsetime = "0.1.36" 23 | daemonize-simple = "0.1.6" 24 | dnsstamps = "0.1.10" 25 | educe = { version = "0.6.0", features = ["full"] } 26 | env_logger = { version = "0.11.8", default-features = false, features = [ 27 | "humantime", 28 | ] } 29 | futures = { version = "0.3.31", features = ["async-await"] } 30 | hyper = { version = "1.6.0", default-features = false, features = [ 31 | "server", 32 | "http1", 33 | ], optional = true } 34 | hyper-util = { version = "0.1.12", features = ["tokio"] } 35 | http-body-util = "0.1.3" 36 | ipext = "0.1.0" 37 | libsodium-sys-stable = "1.22.3" 38 | log = { version = "0.4.27", features = ["std", "release_max_level_debug"] } 39 | mimalloc = { version = "0.1.46", default-features = false } 40 | parking_lot = "0.12.3" 41 | rand = "0.9.1" 42 | rlimit = "0.10.2" 43 | rustc-hash = "2.1.1" 44 | serde = "1.0.219" 45 | serde_derive = "1.0.219" 46 | serde-big-array = "0.5.1" 47 | sieve-cache = "1.1.5" 48 | siphasher = "1.0.1" 49 | slabigator = "0.9.5" 50 | socket2 = "0.5.9" 51 | tokio = { version = "1.45.0", features = [ 52 | "net", 53 | "io-std", 54 | "io-util", 55 | "fs", 56 | "time", 57 | "rt-multi-thread", 58 | ] } 59 | toml = "0.8.22" 60 | 61 | [target.'cfg(target_family = "unix")'.dependencies] 62 | privdrop = "0.5.5" 63 | 64 | [dependencies.prometheus] 65 | optional = true 66 | package = "prometheus" 67 | version = "0.13.4" 68 | default-features = false 69 | features = ["process"] 70 | 71 | [features] 72 | default = ["metrics"] 73 | metrics = ["hyper", "prometheus"] 74 | 75 | [package.metadata.deb] 76 | extended-description = """\ 77 | An easy to install, high-performance, zero maintenance proxy to run an \ 78 | encrypted DNS server.""" 79 | assets = [ 80 | [ 81 | "target/release/encrypted-dns", 82 | "usr/bin/", 83 | "755", 84 | ], 85 | [ 86 | "README.md", 87 | "usr/share/doc/encrypted-dns/README.md", 88 | "644", 89 | ], 90 | [ 91 | "example-encrypted-dns.toml", 92 | "usr/share/doc/encrypted-dns/example-encrypted-dns.toml", 93 | "644", 94 | ], 95 | ] 96 | section = "network" 97 | depends = "$auto" 98 | priority = "optional" 99 | 100 | [profile.release] 101 | codegen-units = 1 102 | incremental = false 103 | panic = "abort" 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Frank Denis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Encrypted DNS Server](logo.png) 2 | ![Github CI status](https://img.shields.io/github/actions/workflow/status/jedisct1/encrypted-dns-server/test.yml?branch=master) 3 | [![Gitter chat](https://badges.gitter.im/gitter.svg)](https://gitter.im/dnscrypt-operators/Lobby) 4 | 5 | An easy to install, high-performance, zero maintenance proxy to run an encrypted DNS server. 6 | 7 | ![Dashboard](dashboard.png) 8 | 9 | ## Protocols 10 | 11 | The proxy supports the following protocols: 12 | 13 | - [DNSCrypt v2](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/DNSCRYPT-V2-PROTOCOL.txt) 14 | - [Anonymized DNSCrypt](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/ANONYMIZED-DNSCRYPT.txt) 15 | - DNS-over-HTTP (DoH) forwarding 16 | 17 | All of these can be served simultaneously, on the same port (usually port 443). The proxy automatically detects what protocol is being used by each client. 18 | 19 | ## Installation 20 | 21 | ### Option 1: precompiled x86_64 binary 22 | 23 | Debian packages, archives for Linux and Windows [can be downloaded here](https://github.com/jedisct1/encrypted-dns-server/releases/latest). 24 | 25 | Nothing else has to be installed. The server doesn't require any external dependencies. 26 | 27 | In the Debian package, the example configuration file can be found in `/usr/share/doc/encrypted-dns/`. 28 | 29 | ### Option 2: compilation from source code 30 | 31 | The proxy requires rust >= 1.0.39 or rust-nightly. 32 | 33 | Rust can installed with: 34 | 35 | ```sh 36 | curl -sSf https://sh.rustup.rs | bash -s -- -y --default-toolchain nightly 37 | source $HOME/.cargo/env 38 | ``` 39 | 40 | Once rust is installed, the proxy can be compiled and installed as follows: 41 | 42 | ```sh 43 | cargo install encrypted-dns 44 | strip ~/.cargo/bin/encrypted-dns 45 | ``` 46 | 47 | The executable file will be copied to `~/.cargo/bin/encrypted-dns` by default. 48 | 49 | ### Options 3: Docker 50 | 51 | [dnscrypt-server-docker](https://github.com/dnscrypt/dnscrypt-server-docker) is the most popular way to deploy an encrypted DNS server. 52 | 53 | This Docker image that includes a caching DNS resolver, the encrypted DNS proxy, and scripts to automatically configure everything. 54 | 55 | ## Setup 56 | 57 | The proxy requires a recursive DNS resolver, such as Knot, PowerDNS or Unbound. 58 | 59 | That resolver can run locally and only respond to `127.0.0.1`. External resolvers such as Quad9 or Cloudflare DNS can also be used, but this may be less reliable due to rate limits. 60 | 61 | In order to support DoH in addition to DNSCrypt, a DoH proxy must be running as well. [rust-doh](https://github.com/jedisct1/rust-doh) is the recommended DoH proxy server. DoH support is optional, as it is currently way more complicated to setup than DNSCrypt due to certificate management. 62 | 63 | Make a copy of the `example-encrypted-dns.toml` configuration file named `encrypted-dns.toml`. 64 | 65 | Then, review the [`encrypted-dns.toml`](https://raw.githubusercontent.com/jedisct1/encrypted-dns-server/master/example-encrypted-dns.toml) file. This is where all the parameters can be configured, including the IP addresses to listen to. 66 | 67 | You should probably at least change the `listen_addrs` and `provider_name` settings. 68 | 69 | Start the proxy. It will automatically create a new provider key pair if there isn't any. 70 | 71 | The DNS stamps are printed. They can be used directly with [`dnscrypt-proxy`](https://github.com/dnscrypt/dnscrypt-proxy/). 72 | 73 | There is nothing else to do. Certificates are automatically generated and rotated. 74 | 75 | ## Migrating from dnscrypt-wrapper 76 | 77 | If you are currently running an encrypted DNS server using [`dnscrypt-wrapper`](https://github.com/cofyc/dnscrypt-wrapper), moving to the new proxy is simple: 78 | 79 | - Double check that the provider name in `encrypted-dns.toml` matches the one you previously configured. If you forgot it, it can be recovered [from its DNS stamp](https://dnscrypt.info/stamps/). 80 | - Run `encrypted-dns --import-from-dnscrypt-wrapper secret.key`, with `secret.key` being the file with the `dnscrypt-wrapper` provider secret key. 81 | 82 | Done. Your server is now running the new proxy. 83 | 84 | ## Built-in DNS cache 85 | 86 | The proxy includes a key cache, as well as a DNS cache to significantly reduce the load on upstream servers. 87 | 88 | In addition, if a server is slow or unresponsive, expired cached queries will be returned, ensuring that popular domain names always keep being served. 89 | 90 | ## State file 91 | 92 | The proxy creates and updates a file named `encrypted-dns.state` by default. That file contains the provider secret key, as well as certificates and encryption keys. 93 | 94 | Do not delete the file, unless you want to change parameters (such as the provider name), and keep it secret, or the keys will be lost. 95 | 96 | Putting it in a directory that is only readable by the super-user is not a bad idea. 97 | 98 | ## Filtering 99 | 100 | Domains can be filtered directly by the proxy, see the `[filtering]` section of the configuration file. Note: Filtering only works with the DNSCrypt protocol and does not apply to DNS-over-HTTP (DoH) forwarding. 101 | 102 | ## Access control 103 | 104 | Access control can be enabled in the `[access_control]` section and configured with the `query_meta` configuration value of `dnscrypt-proxy`. 105 | 106 | ## Prometheus metrics 107 | 108 | Prometheus metrics can optionally be enabled in order to monitor performance, cache efficiency, and more. 109 | 110 | ## Anonymized DNSCrypt 111 | 112 | Enabling Anonymized DNSCrypt allows the server to be used as an encrypted DNS relay. 113 | -------------------------------------------------------------------------------- /dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DNSCrypt/encrypted-dns-server/1a4ca3bc99dccefaa61638b8a008ee36a4a495fa/dashboard.png -------------------------------------------------------------------------------- /example-encrypted-dns.toml: -------------------------------------------------------------------------------- 1 | #################################################### 2 | # # 3 | # Encrypted DNS Server configuration # 4 | # # 5 | #################################################### 6 | 7 | 8 | 9 | ################################## 10 | # Global settings # 11 | ################################## 12 | 13 | 14 | ## IP addresses and ports to listen to, as well as their external IP 15 | ## If there is no NAT involved, `local` and `external` can be the same. 16 | ## As many addresses as needed can be configured here, IPv4 and/or IPv6. 17 | ## You should at least change the `external` IP address. 18 | 19 | ### Example with both IPv4 and IPv6 addresses: 20 | # listen_addrs = [ 21 | # { local = "0.0.0.0:443", external = "198.51.100.1:443" }, 22 | # { local = "[::]:443", external = "[2001:db8::1]:443" } 23 | # ] 24 | 25 | listen_addrs = [ 26 | { local = "0.0.0.0:443", external = "198.51.100.1:443" } 27 | ] 28 | 29 | 30 | ## Upstream DNS server and port 31 | 32 | upstream_addr = "9.9.9.9:53" 33 | 34 | 35 | ## File name to save the state to 36 | 37 | state_file = "encrypted-dns.state" 38 | 39 | 40 | ## UDP timeout in seconds 41 | 42 | udp_timeout = 10 43 | 44 | 45 | ## TCP timeout in seconds 46 | 47 | tcp_timeout = 10 48 | 49 | 50 | ## Maximum active UDP sockets 51 | 52 | udp_max_active_connections = 1000 53 | 54 | 55 | ## Maximum active TCP connections 56 | 57 | tcp_max_active_connections = 250 58 | 59 | 60 | ## Optional IP address to connect to upstream servers from. 61 | ## Leave commented/undefined to automatically select it. 62 | 63 | # external_addr = "0.0.0.0" 64 | 65 | 66 | ## Built-in DNS cache capacity 67 | 68 | cache_capacity = 100000 69 | 70 | 71 | ## DNS cache: minimum TTL 72 | 73 | cache_ttl_min = 3600 74 | 75 | 76 | ## DNS cache: max TTL 77 | 78 | cache_ttl_max = 86400 79 | 80 | 81 | ## DNS cache: error TTL 82 | 83 | cache_ttl_error = 600 84 | 85 | 86 | ## DNS cache: to avoid bursts of traffic for popular queries when an 87 | ## RRSET expires, hold a TTL received from an upstream server for 88 | ## `client_ttl_holdon` seconds before decreasing it in client responses. 89 | 90 | client_ttl_holdon = 60 91 | 92 | 93 | ## Run as a background process 94 | 95 | daemonize = false 96 | 97 | 98 | ## Log file, when running as a background process 99 | 100 | # log_file = "/tmp/encrypted-dns.log" 101 | 102 | 103 | ## PID file 104 | 105 | # pid_file = "/tmp/encrypted-dns.pid" 106 | 107 | 108 | ## User name to drop privileges to, when started as root. 109 | 110 | # user = "nobody" 111 | 112 | 113 | ## Group name to drop privileges to, when started as root. 114 | 115 | # group = "nogroup" 116 | 117 | 118 | ## Path to chroot() to, when started as root. 119 | ## The path to the state file is relative to the chroot base. 120 | 121 | # chroot = "/var/empty" 122 | 123 | 124 | ## Queries sent to that name will return the client IP address. 125 | ## This can be very useful for debugging, or to check that relaying works. 126 | 127 | my_ip = "my.ip" 128 | 129 | 130 | #################################### 131 | # DNSCrypt settings # 132 | #################################### 133 | 134 | [dnscrypt] 135 | 136 | ## Provider name (with or without the `2.dnscrypt-cert.` prefix) 137 | 138 | provider_name = "secure.dns.test" 139 | 140 | 141 | ## Does the server support DNSSEC? 142 | 143 | dnssec = true 144 | 145 | 146 | ## Does the server always returns correct answers (no filtering, including ad blocking)? 147 | 148 | no_filters = true 149 | 150 | 151 | ## Set to `true` if the server doesn't keep any information that can be used to identify users 152 | 153 | no_logs = true 154 | 155 | 156 | ## Key cache capacity, per certificate 157 | 158 | key_cache_capacity = 10000 159 | 160 | 161 | 162 | ############################### 163 | # TLS settings # 164 | ############################### 165 | 166 | [tls] 167 | 168 | ## Where to proxy TLS connections to (e.g. DoH server) 169 | 170 | # upstream_addr = "127.0.0.1:4343" 171 | 172 | 173 | 174 | ####################################### 175 | # Server-side filtering # 176 | ####################################### 177 | 178 | [filtering] 179 | 180 | ## List of domains to block, one per line 181 | 182 | # domain_blacklist = "/etc/domain_blacklist.txt" 183 | 184 | 185 | ## List of undelegated TLDs 186 | ## This is the list of nonexistent TLDs that queries are frequently observed for, 187 | ## but will never resolve to anything. The server will immediately return a 188 | ## synthesized NXDOMAIN response instead of hitting root servers. 189 | 190 | # undelegated_list = "/etc/undelegated.txt" 191 | 192 | 193 | ## Ignore A and AAAA queries for unqualified host names. 194 | 195 | # ignore_unqualified_hostnames = true 196 | 197 | 198 | 199 | ######################### 200 | # Metrics # 201 | ######################### 202 | 203 | # [metrics] 204 | 205 | # type = "prometheus" 206 | # listen_addr = "0.0.0.0:9100" 207 | # path = "/metrics" 208 | 209 | 210 | 211 | ################################ 212 | # Anonymized DNS # 213 | ################################ 214 | 215 | [anonymized_dns] 216 | 217 | # Enable relaying support for Anonymized DNS 218 | 219 | enabled = false 220 | 221 | 222 | # Allowed upstream ports 223 | # This is a list of commonly used ports for encrypted DNS services 224 | 225 | allowed_ports = [ 443, 553, 853, 1443, 2053, 4343, 4434, 4443, 5353, 5443, 8443, 15353 ] 226 | 227 | 228 | # Allow all ports >= 1024 in addition to the list above 229 | 230 | allow_non_reserved_ports = false 231 | 232 | 233 | # Blacklisted upstream IP addresses 234 | 235 | blacklisted_ips = [ "93.184.216.34" ] 236 | 237 | 238 | 239 | 240 | ################################ 241 | # Access control # 242 | ################################ 243 | 244 | [access_control] 245 | 246 | # Enable access control 247 | 248 | enabled = false 249 | 250 | # Only allow access to client queries including one of these random tokens 251 | # Tokens can be configured in the `query_meta` section of `dnscrypt-proxy` as 252 | # `query_meta = ["token:..."]` -- Replace ... with the token to use by the client. 253 | # Example: `query_meta = ["token:Y2oHkDJNHz"]` 254 | 255 | tokens = ["Y2oHkDJNHz", "G5zY3J5cHQtY", "C5zZWN1cmUuZG5z"] 256 | 257 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DNSCrypt/encrypted-dns-server/1a4ca3bc99dccefaa61638b8a008ee36a4a495fa/logo.png -------------------------------------------------------------------------------- /src/anonymized_dns.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hasher; 2 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 3 | use std::sync::Arc; 4 | 5 | use byteorder::{BigEndian, ByteOrder}; 6 | use ipext::IpExt; 7 | use siphasher::sip128::Hasher128; 8 | use tokio::net::UdpSocket; 9 | 10 | use crate::errors::*; 11 | use crate::*; 12 | 13 | pub const ANONYMIZED_DNSCRYPT_QUERY_MAGIC: [u8; 10] = 14 | [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00]; 15 | 16 | pub const ANONYMIZED_DNSCRYPT_OVERHEAD: usize = 16 + 2; 17 | 18 | pub const RELAYED_CERT_CACHE_SIZE: usize = 1000; 19 | pub const RELAYED_CERT_CACHE_TTL: u32 = 600; 20 | 21 | pub async fn handle_anonymized_dns( 22 | globals: Arc, 23 | client_ctx: ClientCtx, 24 | relayed_packet: &[u8], 25 | ) -> Result<(), Error> { 26 | ensure!( 27 | relayed_packet.len() > ANONYMIZED_DNSCRYPT_OVERHEAD, 28 | "Short packet" 29 | ); 30 | let ip_bin = &relayed_packet[..16]; 31 | let ip_v6 = Ipv6Addr::new( 32 | BigEndian::read_u16(&ip_bin[0..2]), 33 | BigEndian::read_u16(&ip_bin[2..4]), 34 | BigEndian::read_u16(&ip_bin[4..6]), 35 | BigEndian::read_u16(&ip_bin[6..8]), 36 | BigEndian::read_u16(&ip_bin[8..10]), 37 | BigEndian::read_u16(&ip_bin[10..12]), 38 | BigEndian::read_u16(&ip_bin[12..14]), 39 | BigEndian::read_u16(&ip_bin[14..16]), 40 | ); 41 | let ip = match ip_v6.to_ipv4() { 42 | Some(ip_v4) => IpAddr::V4(ip_v4), 43 | None => IpAddr::V6(ip_v6), 44 | }; 45 | #[cfg(feature = "metrics")] 46 | globals.varz.anonymized_queries.inc(); 47 | 48 | ensure!(IpExt::is_global(&ip), "Forbidden upstream address"); 49 | ensure!( 50 | !globals.anonymized_dns_blacklisted_ips.contains(&ip), 51 | "Blacklisted upstream IP" 52 | ); 53 | let port = BigEndian::read_u16(&relayed_packet[16..18]); 54 | ensure!( 55 | (globals.anonymized_dns_allow_non_reserved_ports && port >= 1024) 56 | || globals.anonymized_dns_allowed_ports.contains(&port), 57 | "Forbidden upstream port" 58 | ); 59 | let upstream_address = SocketAddr::new(ip, port); 60 | ensure!( 61 | !globals.listen_addrs.contains(&upstream_address) 62 | && globals.external_addr != Some(upstream_address), 63 | "Would be relaying to self" 64 | ); 65 | let encrypted_packet = &relayed_packet[ANONYMIZED_DNSCRYPT_OVERHEAD..]; 66 | let encrypted_packet_len = encrypted_packet.len(); 67 | ensure!( 68 | encrypted_packet_len >= ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len() + DNS_HEADER_SIZE 69 | && encrypted_packet_len <= DNSCRYPT_UDP_QUERY_MAX_SIZE, 70 | "Unexpected encapsulated query length" 71 | ); 72 | ensure!( 73 | encrypted_packet_len > 8 && [0u8, 0, 0, 0, 0, 0, 0, 1] != encrypted_packet[..8], 74 | "Protocol confusion with QUIC" 75 | ); 76 | debug_assert!(DNSCRYPT_UDP_QUERY_MIN_SIZE > ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()); 77 | ensure!( 78 | encrypted_packet[..ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()] 79 | != ANONYMIZED_DNSCRYPT_QUERY_MAGIC, 80 | "Loop detected" 81 | ); 82 | let ext_socket = match globals.external_addr { 83 | Some(x) => UdpSocket::bind(x).await?, 84 | None => match upstream_address { 85 | SocketAddr::V4(_) => { 86 | UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))).await? 87 | } 88 | SocketAddr::V6(s) => { 89 | UdpSocket::bind(SocketAddr::V6(SocketAddrV6::new( 90 | Ipv6Addr::UNSPECIFIED, 91 | 0, 92 | s.flowinfo(), 93 | s.scope_id(), 94 | ))) 95 | .await? 96 | } 97 | }, 98 | }; 99 | ext_socket.connect(&upstream_address).await?; 100 | ext_socket.send(encrypted_packet).await?; 101 | let mut response = vec![0u8; DNSCRYPT_UDP_RESPONSE_MAX_SIZE]; 102 | let (response_len, is_certificate_response) = loop { 103 | let fut = ext_socket.recv_from(&mut response[..]); 104 | let (response_len, response_addr) = fut.await?; 105 | if response_addr != upstream_address { 106 | continue; 107 | } 108 | if is_encrypted_response(&response, response_len) { 109 | break (response_len, false); 110 | } 111 | if is_certificate_response(&response, encrypted_packet) { 112 | break (response_len, true); 113 | } 114 | }; 115 | response.truncate(response_len); 116 | if is_certificate_response { 117 | let mut hasher = globals.hasher; 118 | hasher.write(&relayed_packet[..ANONYMIZED_DNSCRYPT_OVERHEAD]); 119 | hasher.write(&dns::qname(encrypted_packet)?); 120 | let packet_hash = hasher.finish128().as_u128(); 121 | let cached_response = { 122 | match globals.cert_cache.lock().get(&packet_hash) { 123 | None => None, 124 | Some(response) if !(*response).has_expired() => { 125 | trace!("Relayed certificate cached"); 126 | let mut cached_response = (*response).clone(); 127 | cached_response.set_tid(dns::tid(encrypted_packet)); 128 | Some(cached_response.into_response()) 129 | } 130 | Some(_) => { 131 | trace!("Relayed certificate expired"); 132 | None 133 | } 134 | } 135 | }; 136 | match cached_response { 137 | None => { 138 | globals.cert_cache.lock().insert( 139 | packet_hash, 140 | CachedResponse::new(&globals.cert_cache, response.clone()), 141 | ); 142 | } 143 | Some(cached_response) => response = cached_response, 144 | } 145 | } 146 | 147 | #[cfg(feature = "metrics")] 148 | globals.varz.anonymized_responses.inc(); 149 | 150 | respond_to_query(client_ctx, response).await 151 | } 152 | 153 | #[inline] 154 | fn is_encrypted_response(response: &[u8], response_len: usize) -> bool { 155 | (DNSCRYPT_UDP_RESPONSE_MIN_SIZE..=DNSCRYPT_UDP_RESPONSE_MAX_SIZE).contains(&response_len) 156 | && response[..DNSCRYPT_RESPONSE_MAGIC_SIZE] == DNSCRYPT_RESPONSE_MAGIC 157 | } 158 | 159 | fn is_certificate_response(response: &[u8], query: &[u8]) -> bool { 160 | let prefix = b"2.dnscrypt-cert."; 161 | if !((DNS_HEADER_SIZE + prefix.len() + 4..=DNS_MAX_PACKET_SIZE).contains(&query.len()) 162 | && (DNS_HEADER_SIZE + prefix.len() + 4..=DNS_MAX_PACKET_SIZE).contains(&response.len()) 163 | && dns::tid(response) == dns::tid(query) 164 | && dns::is_response(response) 165 | && !dns::is_response(query)) 166 | { 167 | debug!("Unexpected relayed cert response"); 168 | return false; 169 | } 170 | let qname = match (dns::qname(query), dns::qname(response)) { 171 | (Ok(response_qname), Ok(query_qname)) if response_qname == query_qname => query_qname, 172 | _ => { 173 | debug!("Relayed cert qname response didn't match the query qname"); 174 | return false; 175 | } 176 | }; 177 | if qname.len() <= prefix.len() || &qname[..prefix.len()] != prefix { 178 | debug!("Relayed cert qname response didn't start with the standard prefix"); 179 | return false; 180 | } 181 | true 182 | } 183 | -------------------------------------------------------------------------------- /src/blacklist.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufRead, BufReader}; 3 | use std::path::Path; 4 | use std::sync::Arc; 5 | 6 | use rustc_hash::FxHashMap; 7 | 8 | use crate::errors::*; 9 | 10 | const MAX_ITERATIONS: usize = 5; 11 | 12 | #[derive(Debug)] 13 | struct BlackListInner { 14 | map: FxHashMap, ()>, 15 | } 16 | 17 | #[derive(Clone, Debug)] 18 | pub struct BlackList { 19 | inner: Arc, 20 | max_iterations: usize, 21 | } 22 | 23 | impl BlackList { 24 | pub fn new(map: FxHashMap, ()>, max_iterations: usize) -> Self { 25 | let inner = Arc::new(BlackListInner { map }); 26 | BlackList { 27 | inner, 28 | max_iterations, 29 | } 30 | } 31 | 32 | pub fn load(path: impl AsRef) -> Result { 33 | let mut map = FxHashMap::default(); 34 | let fp = BufReader::new(File::open(path)?); 35 | for (line_nb, line) in fp.lines().enumerate() { 36 | let line = line?; 37 | let mut line = line.trim(); 38 | if line.is_empty() || line.starts_with('#') { 39 | continue; 40 | } 41 | while line.starts_with("*.") { 42 | line = &line[2..]; 43 | } 44 | while line.ends_with('.') { 45 | line = &line[..line.len() - 1]; 46 | } 47 | let qname = line.as_bytes().to_ascii_lowercase(); 48 | if qname.is_empty() { 49 | bail!("Unexpected blacklist rule at line {}", line_nb) 50 | } 51 | map.insert(qname, ()); 52 | } 53 | Ok(BlackList::new(map, MAX_ITERATIONS)) 54 | } 55 | 56 | pub fn find(&self, qname: &[u8]) -> bool { 57 | let qname = qname.to_ascii_lowercase(); 58 | let mut qname = qname.as_slice(); 59 | let map = &self.inner.map; 60 | let mut iterations = self.max_iterations; 61 | while qname.len() >= 4 && iterations > 0 { 62 | if map.contains_key(qname) { 63 | return true; 64 | } 65 | let mut it = qname.splitn(2, |x| *x == b'.'); 66 | if it.next().is_none() { 67 | break; 68 | } 69 | qname = match it.next() { 70 | None => break, 71 | Some(qname) => qname, 72 | }; 73 | iterations -= 1; 74 | } 75 | false 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use coarsetime::{Duration, Instant}; 4 | use parking_lot::{Mutex, MutexGuard}; 5 | use sieve_cache::SieveCache; 6 | 7 | use crate::dns; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct CachedResponse { 11 | response: Vec, 12 | expiry: Instant, 13 | original_ttl: u32, 14 | } 15 | 16 | impl CachedResponse { 17 | pub fn new(cache: &Cache, response: Vec) -> Self { 18 | let ttl = dns::min_ttl(&response, cache.ttl_min, cache.ttl_max, cache.ttl_error) 19 | .unwrap_or(cache.ttl_error); 20 | let expiry = Instant::recent() + Duration::from_secs(u64::from(ttl)); 21 | CachedResponse { 22 | response, 23 | expiry, 24 | original_ttl: ttl, 25 | } 26 | } 27 | 28 | #[inline] 29 | pub fn set_tid(&mut self, tid: u16) { 30 | dns::set_tid(&mut self.response, tid) 31 | } 32 | 33 | #[inline] 34 | pub fn into_response(self) -> Vec { 35 | self.response 36 | } 37 | 38 | #[inline] 39 | pub fn has_expired(&self) -> bool { 40 | Instant::recent() > self.expiry 41 | } 42 | 43 | #[inline] 44 | pub fn ttl(&self) -> u32 { 45 | (self.expiry - Instant::recent()).as_secs() as _ 46 | } 47 | 48 | #[inline] 49 | pub fn original_ttl(&self) -> u32 { 50 | self.original_ttl 51 | } 52 | } 53 | 54 | #[derive(Clone, Educe)] 55 | #[educe(Debug)] 56 | pub struct Cache { 57 | #[educe(Debug(ignore))] 58 | cache: Arc>>, 59 | pub ttl_min: u32, 60 | pub ttl_max: u32, 61 | pub ttl_error: u32, 62 | } 63 | 64 | impl Cache { 65 | pub fn new( 66 | sieve_cache: SieveCache, 67 | ttl_min: u32, 68 | ttl_max: u32, 69 | ttl_error: u32, 70 | ) -> Self { 71 | Cache { 72 | cache: Arc::new(Mutex::new(sieve_cache)), 73 | ttl_min, 74 | ttl_max, 75 | ttl_error, 76 | } 77 | } 78 | 79 | #[inline] 80 | pub fn lock(&self) -> MutexGuard<'_, SieveCache> { 81 | self.cache.lock() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::mem; 3 | use std::net::{IpAddr, SocketAddr}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use tokio::io::AsyncWriteExt; 7 | 8 | use crate::crypto::*; 9 | use crate::dnscrypt_certs::*; 10 | use crate::errors::*; 11 | 12 | #[derive(Serialize, Deserialize, Debug, Clone)] 13 | pub struct AccessControlConfig { 14 | pub enabled: bool, 15 | pub tokens: Vec, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Debug, Clone)] 19 | pub struct AnonymizedDNSConfig { 20 | pub enabled: bool, 21 | pub allowed_ports: Vec, 22 | pub allow_non_reserved_ports: Option, 23 | pub blacklisted_ips: Vec, 24 | } 25 | 26 | #[cfg(feature = "metrics")] 27 | #[derive(Serialize, Deserialize, Debug, Clone)] 28 | pub struct MetricsConfig { 29 | pub r#type: String, 30 | pub listen_addr: SocketAddr, 31 | pub path: String, 32 | } 33 | 34 | #[derive(Serialize, Deserialize, Debug, Clone)] 35 | pub struct DNSCryptConfig { 36 | pub enabled: Option, 37 | pub provider_name: String, 38 | pub key_cache_capacity: usize, 39 | pub dnssec: bool, 40 | pub no_filters: bool, 41 | pub no_logs: bool, 42 | } 43 | 44 | #[derive(Serialize, Deserialize, Debug, Clone)] 45 | pub struct TLSConfig { 46 | pub upstream_addr: Option, 47 | } 48 | 49 | #[derive(Serialize, Deserialize, Debug, Clone)] 50 | pub struct ListenAddrConfig { 51 | pub local: SocketAddr, 52 | pub external: SocketAddr, 53 | } 54 | 55 | #[derive(Serialize, Deserialize, Debug, Clone)] 56 | pub struct FilteringConfig { 57 | pub domain_blacklist: Option, 58 | pub undelegated_list: Option, 59 | pub ignore_unqualified_hostnames: Option, 60 | } 61 | 62 | #[derive(Serialize, Deserialize, Debug, Clone)] 63 | pub struct Config { 64 | pub listen_addrs: Vec, 65 | pub external_addr: Option, 66 | pub upstream_addr: SocketAddr, 67 | pub state_file: PathBuf, 68 | pub udp_timeout: u32, 69 | pub tcp_timeout: u32, 70 | pub udp_max_active_connections: u32, 71 | pub tcp_max_active_connections: u32, 72 | pub cache_capacity: usize, 73 | pub cache_ttl_min: u32, 74 | pub cache_ttl_max: u32, 75 | pub cache_ttl_error: u32, 76 | pub user: Option, 77 | pub group: Option, 78 | pub chroot: Option, 79 | pub filtering: FilteringConfig, 80 | pub dnscrypt: DNSCryptConfig, 81 | pub tls: TLSConfig, 82 | pub daemonize: bool, 83 | pub pid_file: Option, 84 | pub log_file: Option, 85 | pub my_ip: Option, 86 | pub client_ttl_holdon: Option, 87 | #[cfg(feature = "metrics")] 88 | pub metrics: Option, 89 | pub anonymized_dns: Option, 90 | pub access_control: Option, 91 | } 92 | 93 | impl Config { 94 | pub fn from_string(toml: &str) -> Result { 95 | let config: Config = match toml::from_str(toml) { 96 | Ok(config) => config, 97 | Err(e) => bail!("Parse error in the configuration file: {}", e), 98 | }; 99 | Ok(config) 100 | } 101 | 102 | pub fn from_path(path: impl AsRef) -> Result { 103 | let toml = fs::read_to_string(path)?; 104 | Config::from_string(&toml) 105 | } 106 | } 107 | 108 | #[derive(Serialize, Deserialize, Debug)] 109 | pub struct State { 110 | pub provider_kp: SignKeyPair, 111 | pub dnscrypt_encryption_params_set: Vec, 112 | } 113 | 114 | impl State { 115 | pub fn with_key_pair(provider_kp: SignKeyPair, key_cache_capacity: usize) -> Self { 116 | let dnscrypt_encryption_params_set = 117 | DNSCryptEncryptionParams::new(&provider_kp, key_cache_capacity, None); 118 | State { 119 | provider_kp, 120 | dnscrypt_encryption_params_set, 121 | } 122 | } 123 | 124 | pub fn new(key_cache_capacity: usize) -> Self { 125 | let provider_kp = SignKeyPair::new(); 126 | State::with_key_pair(provider_kp, key_cache_capacity) 127 | } 128 | 129 | pub async fn async_save(&self, path: impl AsRef) -> Result<(), Error> { 130 | let path_tmp = path.as_ref().with_extension("tmp"); 131 | let mut fpb = tokio::fs::OpenOptions::new(); 132 | let fpb = fpb.create(true).write(true); 133 | let mut fp = fpb.open(&path_tmp).await?; 134 | let state_str = toml::to_string(&self)?; 135 | fp.write_all(state_str.as_bytes()).await?; 136 | fp.sync_data().await?; 137 | mem::drop(fp); 138 | tokio::fs::rename(path_tmp, path).await?; 139 | Ok(()) 140 | } 141 | 142 | pub fn from_file(path: impl AsRef, key_cache_capacity: usize) -> Result { 143 | let state_str = fs::read_to_string(path)?; 144 | let mut state: State = toml::from_str(&state_str)?; 145 | for params_set in &mut state.dnscrypt_encryption_params_set { 146 | params_set.add_key_cache(key_cache_capacity); 147 | } 148 | Ok(state) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/crypto.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::hash::Hasher; 3 | use std::ptr; 4 | 5 | use libsodium_sys::*; 6 | use serde_big_array::BigArray; 7 | use siphasher::sip::SipHasher13; 8 | 9 | use crate::errors::*; 10 | 11 | #[derive(Educe)] 12 | #[educe(Default)] 13 | pub struct Signature( 14 | #[educe(Default = [0u8; crypto_sign_BYTES as usize])] [u8; crypto_sign_BYTES as usize], 15 | ); 16 | 17 | impl Signature { 18 | pub fn as_bytes(&self) -> &[u8; crypto_sign_BYTES as usize] { 19 | &self.0 20 | } 21 | 22 | pub fn from_bytes(bytes: [u8; crypto_sign_BYTES as usize]) -> Self { 23 | Signature(bytes) 24 | } 25 | } 26 | 27 | #[derive(Serialize, Deserialize, Educe, Clone)] 28 | #[educe(Default)] 29 | pub struct SignSK( 30 | #[serde(with = "BigArray")] 31 | #[educe(Default = [0u8; crypto_sign_SECRETKEYBYTES as usize])] 32 | [u8; crypto_sign_SECRETKEYBYTES as usize], 33 | ); 34 | 35 | impl SignSK { 36 | pub fn as_bytes(&self) -> &[u8; crypto_sign_SECRETKEYBYTES as usize] { 37 | &self.0 38 | } 39 | 40 | pub fn from_bytes(bytes: [u8; crypto_sign_SECRETKEYBYTES as usize]) -> Self { 41 | SignSK(bytes) 42 | } 43 | 44 | pub fn sign(&self, bytes: &[u8]) -> Signature { 45 | let mut signature = Signature::default(); 46 | let ret = unsafe { 47 | crypto_sign_detached( 48 | signature.0.as_mut_ptr(), 49 | ptr::null_mut(), 50 | bytes.as_ptr(), 51 | bytes.len() as _, 52 | self.as_bytes().as_ptr(), 53 | ) 54 | }; 55 | assert_eq!(ret, 0); 56 | signature 57 | } 58 | } 59 | 60 | #[derive(Debug, Serialize, Deserialize, Default, Clone)] 61 | pub struct SignPK([u8; crypto_sign_PUBLICKEYBYTES as usize]); 62 | 63 | impl SignPK { 64 | pub fn as_bytes(&self) -> &[u8; crypto_sign_PUBLICKEYBYTES as usize] { 65 | &self.0 66 | } 67 | 68 | pub fn from_bytes(bytes: [u8; crypto_sign_PUBLICKEYBYTES as usize]) -> Self { 69 | SignPK(bytes) 70 | } 71 | 72 | pub fn as_string(&self) -> String { 73 | bin2hex(self.as_bytes()) 74 | } 75 | } 76 | 77 | #[derive(Educe, Serialize, Deserialize, Clone)] 78 | #[educe(Debug, Default)] 79 | pub struct SignKeyPair { 80 | #[educe(Debug(ignore))] 81 | pub sk: SignSK, 82 | pub pk: SignPK, 83 | } 84 | 85 | impl SignKeyPair { 86 | pub fn new() -> Self { 87 | let mut kp = SignKeyPair::default(); 88 | unsafe { crypto_sign_keypair(kp.pk.0.as_mut_ptr(), kp.sk.0.as_mut_ptr()) }; 89 | kp 90 | } 91 | } 92 | 93 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 94 | pub struct CryptSK([u8; crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize]); 95 | 96 | impl CryptSK { 97 | pub fn as_bytes( 98 | &self, 99 | ) -> &[u8; crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize] { 100 | &self.0 101 | } 102 | 103 | pub fn from_bytes( 104 | bytes: [u8; crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize], 105 | ) -> Self { 106 | CryptSK(bytes) 107 | } 108 | } 109 | 110 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 111 | pub struct CryptPK([u8; crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize]); 112 | 113 | impl CryptPK { 114 | pub fn as_bytes( 115 | &self, 116 | ) -> &[u8; crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize] { 117 | &self.0 118 | } 119 | 120 | pub fn from_bytes( 121 | bytes: [u8; crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize], 122 | ) -> Self { 123 | CryptPK(bytes) 124 | } 125 | } 126 | 127 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 128 | pub struct CryptKeyPair { 129 | pub sk: CryptSK, 130 | pub pk: CryptPK, 131 | } 132 | 133 | impl CryptKeyPair { 134 | pub fn from_seed( 135 | seed: [u8; crypto_box_curve25519xchacha20poly1305_SEEDBYTES as usize], 136 | ) -> Self { 137 | let mut kp = CryptKeyPair::default(); 138 | unsafe { 139 | crypto_box_curve25519xchacha20poly1305_seed_keypair( 140 | kp.pk.0.as_mut_ptr(), 141 | kp.sk.0.as_mut_ptr(), 142 | seed.as_ptr(), 143 | ) 144 | }; 145 | kp 146 | } 147 | 148 | pub fn compute_shared_key(&self, pk: &[u8]) -> Result { 149 | let mut shared_key = SharedKey::default(); 150 | let res = unsafe { 151 | crypto_box_curve25519xchacha20poly1305_beforenm( 152 | shared_key.0.as_mut_ptr(), 153 | pk.as_ptr(), 154 | self.sk.0.as_ptr(), 155 | ) 156 | }; 157 | ensure!(res == 0, "Weak public key"); 158 | Ok(shared_key) 159 | } 160 | } 161 | 162 | #[derive(Debug, Clone, Default)] 163 | pub struct SharedKey([u8; crypto_box_curve25519xchacha20poly1305_BEFORENMBYTES as usize]); 164 | 165 | impl SharedKey { 166 | pub fn decrypt(&self, nonce: &[u8], encrypted: &[u8]) -> Result, Error> { 167 | let encrypted_len = encrypted.len(); 168 | let mut decrypted = 169 | vec![0u8; encrypted_len - crypto_box_curve25519xchacha20poly1305_MACBYTES as usize]; 170 | let res = unsafe { 171 | libsodium_sys::crypto_box_curve25519xchacha20poly1305_open_easy_afternm( 172 | decrypted.as_mut_ptr(), 173 | encrypted.as_ptr(), 174 | encrypted_len as _, 175 | nonce.as_ptr(), 176 | self.0.as_ptr(), 177 | ) 178 | }; 179 | ensure!(res == 0, "Unable to decrypt"); 180 | let idx = decrypted 181 | .iter() 182 | .rposition(|x| *x != 0x00) 183 | .ok_or_else(|| anyhow!("Padding error"))?; 184 | ensure!(decrypted[idx] == 0x80, "Padding error"); 185 | decrypted.truncate(idx); 186 | Ok(decrypted) 187 | } 188 | 189 | pub fn encrypt_into( 190 | &self, 191 | target: &mut Vec, 192 | nonce: &[u8], 193 | client_nonce: &[u8], 194 | plaintext: Vec, 195 | max_target_size: usize, 196 | ) -> Result<(), Error> { 197 | ensure!( 198 | max_target_size >= crypto_box_curve25519xchacha20poly1305_MACBYTES as usize, 199 | "Max target size too small" 200 | ); 201 | let plaintext_len = plaintext.len(); 202 | let max_padded_plaintext_len = 203 | max_target_size - crypto_box_curve25519xchacha20poly1305_MACBYTES as usize; 204 | let mut hasher = SipHasher13::new(); 205 | hasher.write(&self.0); 206 | hasher.write(client_nonce); 207 | let pad_size: usize = 1 + (hasher.finish() as usize & 0xff); 208 | let mut padded_plaintext_len = (plaintext_len + pad_size) & !63; 209 | if padded_plaintext_len < plaintext_len { 210 | padded_plaintext_len += 256; 211 | } 212 | if padded_plaintext_len > max_padded_plaintext_len { 213 | padded_plaintext_len = max_padded_plaintext_len; 214 | } 215 | ensure!(padded_plaintext_len > plaintext_len, "No room for padding"); 216 | let mut padded_plaintext = plaintext; 217 | padded_plaintext.push(0x80); 218 | while padded_plaintext.len() != padded_plaintext_len { 219 | padded_plaintext.push(0x00); 220 | } 221 | let padded_plaintext_len = padded_plaintext.len(); 222 | let target_header_len = target.len(); 223 | target.resize( 224 | target_header_len 225 | + padded_plaintext_len 226 | + crypto_box_curve25519xchacha20poly1305_MACBYTES as usize, 227 | 0, 228 | ); 229 | let encrypted = &mut target[target_header_len..]; 230 | let res = unsafe { 231 | libsodium_sys::crypto_box_curve25519xchacha20poly1305_easy_afternm( 232 | encrypted.as_mut_ptr(), 233 | padded_plaintext.as_ptr(), 234 | padded_plaintext_len as _, 235 | nonce.as_ptr(), 236 | self.0.as_ptr(), 237 | ) 238 | }; 239 | ensure!(res == 0, "Unable to encrypt"); 240 | Ok(()) 241 | } 242 | } 243 | 244 | pub fn bin2hex(bin: &[u8]) -> String { 245 | let bin_len = bin.len(); 246 | let hex_len = bin_len * 2 + 1; 247 | let mut hex = vec![0u8; hex_len]; 248 | unsafe { 249 | sodium_bin2hex(hex.as_mut_ptr() as *mut _, hex_len, bin.as_ptr(), bin_len); 250 | } 251 | CStr::from_bytes_with_nul(&hex) 252 | .unwrap() 253 | .to_str() 254 | .unwrap() 255 | .to_string() 256 | } 257 | 258 | pub fn init() -> Result<(), Error> { 259 | let res = unsafe { sodium_init() }; 260 | ensure!(res >= 0, "Unable to initialize libsodium"); 261 | Ok(()) 262 | } 263 | -------------------------------------------------------------------------------- /src/dns.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | use std::sync::Arc; 3 | 4 | use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; 5 | 6 | use crate::dnscrypt_certs::*; 7 | use crate::errors::*; 8 | 9 | pub const DNS_MAX_HOSTNAME_SIZE: usize = 256; 10 | pub const DNS_HEADER_SIZE: usize = 12; 11 | pub const DNS_OFFSET_FLAGS: usize = 2; 12 | pub const DNS_MAX_PACKET_SIZE: usize = 0x1600; 13 | 14 | const DNS_MAX_INDIRECTIONS: usize = 16; 15 | const DNS_FLAGS_TC: u16 = 1u16 << 9; 16 | const DNS_FLAGS_QR: u16 = 1u16 << 15; 17 | const DNS_FLAGS_RA: u16 = 1u16 << 7; 18 | const DNS_FLAGS_RD: u16 = 1u16 << 8; 19 | const DNS_FLAGS_CD: u16 = 1u16 << 4; 20 | const DNS_OFFSET_QUESTION: usize = DNS_HEADER_SIZE; 21 | 22 | pub const DNS_TYPE_A: u16 = 1; 23 | pub const DNS_TYPE_AAAA: u16 = 28; 24 | pub const DNS_TYPE_OPT: u16 = 41; 25 | pub const DNS_TYPE_TXT: u16 = 16; 26 | pub const DNS_TYPE_HINFO: u16 = 13; 27 | pub const DNS_CLASS_INET: u16 = 1; 28 | 29 | pub const DNS_RCODE_SERVFAIL: u8 = 2; 30 | pub const DNS_RCODE_NXDOMAIN: u8 = 3; 31 | pub const DNS_RCODE_REFUSED: u8 = 5; 32 | 33 | #[inline] 34 | pub fn rcode(packet: &[u8]) -> u8 { 35 | packet[3] & 0x0f 36 | } 37 | 38 | #[inline] 39 | pub fn set_rcode(packet: &mut [u8], rcode: u8) { 40 | packet[3] = (packet[3] & !0x0f) | rcode; 41 | } 42 | 43 | #[inline] 44 | pub fn rcode_servfail(packet: &[u8]) -> bool { 45 | rcode(packet) == DNS_RCODE_SERVFAIL 46 | } 47 | 48 | #[inline] 49 | pub fn set_rcode_servfail(packet: &mut [u8]) { 50 | set_rcode(packet, DNS_RCODE_SERVFAIL) 51 | } 52 | 53 | #[inline] 54 | pub fn rcode_refused(packet: &[u8]) -> bool { 55 | rcode(packet) == DNS_RCODE_REFUSED 56 | } 57 | 58 | #[inline] 59 | pub fn set_rcode_refused(packet: &mut [u8]) { 60 | set_rcode(packet, DNS_RCODE_REFUSED) 61 | } 62 | 63 | #[inline] 64 | pub fn rcode_nxdomain(packet: &[u8]) -> bool { 65 | rcode(packet) == DNS_RCODE_NXDOMAIN 66 | } 67 | 68 | #[inline] 69 | pub fn set_rcode_nxdomain(packet: &mut [u8]) { 70 | set_rcode(packet, DNS_RCODE_NXDOMAIN) 71 | } 72 | 73 | #[inline] 74 | pub fn qdcount(packet: &[u8]) -> u16 { 75 | BigEndian::read_u16(&packet[4..]) 76 | } 77 | 78 | #[inline] 79 | pub fn ancount(packet: &[u8]) -> u16 { 80 | BigEndian::read_u16(&packet[6..]) 81 | } 82 | 83 | fn ancount_inc(packet: &mut [u8]) -> Result<(), Error> { 84 | let mut ancount = ancount(packet); 85 | ensure!(ancount < 0xffff, "Too many answer records"); 86 | ancount += 1; 87 | BigEndian::write_u16(&mut packet[6..], ancount); 88 | Ok(()) 89 | } 90 | 91 | #[inline] 92 | fn nscount(packet: &[u8]) -> u16 { 93 | BigEndian::read_u16(&packet[8..]) 94 | } 95 | 96 | #[inline] 97 | pub fn arcount(packet: &[u8]) -> u16 { 98 | BigEndian::read_u16(&packet[10..]) 99 | } 100 | 101 | fn arcount_inc(packet: &mut [u8]) -> Result<(), Error> { 102 | let mut arcount = arcount(packet); 103 | ensure!(arcount < 0xffff, "Too many additional records"); 104 | arcount += 1; 105 | BigEndian::write_u16(&mut packet[10..], arcount); 106 | Ok(()) 107 | } 108 | 109 | #[inline] 110 | fn arcount_clear(packet: &mut [u8]) -> Result<(), Error> { 111 | BigEndian::write_u16(&mut packet[10..], 0); 112 | Ok(()) 113 | } 114 | 115 | #[inline] 116 | pub fn an_ns_ar_count_clear(packet: &mut [u8]) { 117 | packet[6..12].iter_mut().for_each(|x| *x = 0); 118 | } 119 | 120 | #[inline] 121 | pub fn tid(packet: &[u8]) -> u16 { 122 | BigEndian::read_u16(&packet[0..]) 123 | } 124 | 125 | #[inline] 126 | pub fn set_tid(packet: &mut [u8], tid: u16) { 127 | BigEndian::write_u16(&mut packet[0..], tid); 128 | } 129 | 130 | #[inline] 131 | pub fn set_flags(packet: &mut [u8], flags: u16) { 132 | BigEndian::write_u16(&mut packet[DNS_OFFSET_FLAGS..], flags); 133 | } 134 | 135 | #[inline] 136 | pub fn authoritative_response(packet: &mut [u8]) { 137 | let current_rd_cd_flags = 138 | BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]) & (DNS_FLAGS_CD | DNS_FLAGS_RD); 139 | set_flags(packet, current_rd_cd_flags | DNS_FLAGS_QR | DNS_FLAGS_RA); 140 | } 141 | 142 | #[inline] 143 | pub fn truncate(packet: &mut [u8]) { 144 | let current_flags = BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]); 145 | BigEndian::write_u16( 146 | &mut packet[DNS_OFFSET_FLAGS..], 147 | current_flags | DNS_FLAGS_TC | DNS_FLAGS_QR | DNS_FLAGS_RA, 148 | ); 149 | } 150 | 151 | #[inline] 152 | pub fn is_response(packet: &[u8]) -> bool { 153 | BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]) & DNS_FLAGS_QR == DNS_FLAGS_QR 154 | } 155 | 156 | #[inline] 157 | pub fn is_truncated(packet: &[u8]) -> bool { 158 | BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]) & DNS_FLAGS_TC == DNS_FLAGS_TC 159 | } 160 | 161 | pub fn qname(packet: &[u8]) -> Result, Error> { 162 | debug_assert!(std::usize::MAX > 0xffff); 163 | debug_assert!(DNS_MAX_HOSTNAME_SIZE > 0xff); 164 | let packet_len = packet.len(); 165 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 166 | ensure!(qdcount(packet) == 1, "Unexpected query count"); 167 | let mut offset = DNS_HEADER_SIZE; 168 | let mut qname = Vec::with_capacity(DNS_MAX_HOSTNAME_SIZE); 169 | loop { 170 | ensure!(offset < packet_len, "Short packet"); 171 | match packet[offset] as usize { 172 | label_len if label_len & 0xc0 == 0xc0 => bail!("Indirections"), 173 | 0 => { 174 | if qname.is_empty() { 175 | qname.push(b'.') 176 | } 177 | break; 178 | } 179 | label_len => { 180 | ensure!(label_len < 0x40, "Long label"); 181 | ensure!(packet_len - offset > 1, "Short packet"); 182 | offset += 1; 183 | ensure!(packet_len - offset > label_len, "Short packet"); 184 | if !qname.is_empty() { 185 | qname.push(b'.') 186 | } 187 | ensure!( 188 | qname.len() < DNS_MAX_HOSTNAME_SIZE - label_len, 189 | "Name too long" 190 | ); 191 | qname.extend_from_slice(&packet[offset..offset + label_len]); 192 | offset += label_len; 193 | } 194 | } 195 | } 196 | Ok(qname) 197 | } 198 | 199 | pub fn normalize_qname(packet: &mut [u8]) -> Result<(), Error> { 200 | debug_assert!(std::usize::MAX > 0xffff); 201 | debug_assert!(DNS_MAX_HOSTNAME_SIZE > 0xff); 202 | let packet_len = packet.len(); 203 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 204 | ensure!(qdcount(packet) == 1, "Unexpected query count"); 205 | let mut offset = DNS_HEADER_SIZE; 206 | loop { 207 | ensure!(offset < packet_len, "Short packet"); 208 | match packet[offset] as usize { 209 | label_len if label_len & 0xc0 == 0xc0 => bail!("Indirections"), 210 | 0 => { 211 | break; 212 | } 213 | label_len => { 214 | ensure!(label_len < 0x40, "Long label"); 215 | ensure!(packet_len - offset > 1, "Short packet"); 216 | offset += 1; 217 | ensure!(packet_len - offset > label_len, "Short packet"); 218 | ensure!( 219 | offset - DNS_HEADER_SIZE < DNS_MAX_HOSTNAME_SIZE - label_len, 220 | "Name too long" 221 | ); 222 | packet[offset..offset + label_len] 223 | .iter_mut() 224 | .for_each(|x| *x = x.to_ascii_lowercase()); 225 | offset += label_len; 226 | } 227 | } 228 | } 229 | Ok(()) 230 | } 231 | 232 | pub fn qname_tld(qname: &[u8]) -> &[u8] { 233 | qname.rsplit(|c| *c == b'.').next().unwrap_or_default() 234 | } 235 | 236 | pub fn recase_qname(packet: &mut [u8], qname: &[u8]) -> Result<(), Error> { 237 | debug_assert!(std::usize::MAX > 0xffff); 238 | let packet_len = packet.len(); 239 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 240 | ensure!(qdcount(packet) == 1, "Unexpected query count"); 241 | let qname_len = qname.len(); 242 | let mut offset = DNS_HEADER_SIZE; 243 | let mut qname_offset = 0; 244 | loop { 245 | ensure!(offset < packet_len, "Short packet"); 246 | match packet[offset] as usize { 247 | label_len if label_len & 0xc0 == 0xc0 => bail!("Indirections"), 248 | 0 => { 249 | ensure!( 250 | (qname_len == 1 && qname[0] == b'.') || qname_offset == qname_len, 251 | "Unterminated reference qname" 252 | ); 253 | break; 254 | } 255 | label_len => { 256 | ensure!(label_len < 0x40, "Long label"); 257 | ensure!(packet_len - offset > 1, "Short packet"); 258 | ensure!(qname_offset < qname_len, "Short reference qname"); 259 | offset += 1; 260 | if qname_offset != 0 { 261 | ensure!(qname[qname_offset] == b'.', "Non-matching reference qname"); 262 | qname_offset += 1; 263 | } 264 | ensure!(packet_len - offset > label_len, "Short packet"); 265 | ensure!( 266 | qname_len - qname_offset >= label_len, 267 | "Short reference qname" 268 | ); 269 | packet[offset..offset + label_len] 270 | .iter_mut() 271 | .zip(&qname[qname_offset..qname_offset + label_len]) 272 | .for_each(|(a, b)| { 273 | debug_assert!(a.eq_ignore_ascii_case(b)); 274 | *a = *b 275 | }); 276 | offset += label_len; 277 | qname_offset += label_len; 278 | } 279 | } 280 | } 281 | Ok(()) 282 | } 283 | 284 | fn skip_name(packet: &[u8], offset: usize) -> Result { 285 | let packet_len = packet.len(); 286 | ensure!(offset < packet_len - 1, "Short packet"); 287 | let mut qname_len: usize = 0; 288 | let mut offset = offset; 289 | loop { 290 | let label_len = match packet[offset] as usize { 291 | label_len if label_len & 0xc0 == 0xc0 => { 292 | ensure!(packet_len - offset >= 2, "Incomplete offset"); 293 | offset += 2; 294 | break; 295 | } 296 | label_len => label_len, 297 | }; 298 | ensure!(label_len < 0x40, "Long label"); 299 | ensure!( 300 | packet_len - offset - 1 > label_len, 301 | "Malformed packet with an out-of-bounds name" 302 | ); 303 | qname_len += label_len + 1; 304 | ensure!(qname_len <= DNS_MAX_HOSTNAME_SIZE, "Name too long"); 305 | offset += label_len + 1; 306 | if label_len == 0 { 307 | break; 308 | } 309 | } 310 | Ok(offset) 311 | } 312 | 313 | fn traverse_rrs Result<(), Error>>( 314 | packet: &[u8], 315 | mut offset: usize, 316 | rrcount: usize, 317 | mut cb: F, 318 | ) -> Result { 319 | let packet_len = packet.len(); 320 | for _ in 0..rrcount { 321 | offset = skip_name(packet, offset)?; 322 | ensure!(packet_len - offset >= 10, "Short packet"); 323 | let rdlen = BigEndian::read_u16(&packet[offset + 8..]) as usize; 324 | ensure!( 325 | packet_len - offset >= 10 + rdlen, 326 | "Record length would exceed packet length" 327 | ); 328 | cb(offset)?; 329 | offset += 10; 330 | offset += rdlen; 331 | } 332 | Ok(offset) 333 | } 334 | 335 | fn traverse_rrs_mut Result<(), Error>>( 336 | packet: &mut [u8], 337 | mut offset: usize, 338 | rrcount: usize, 339 | mut cb: F, 340 | ) -> Result { 341 | let packet_len = packet.len(); 342 | for _ in 0..rrcount { 343 | offset = skip_name(packet, offset)?; 344 | ensure!(packet_len - offset >= 10, "Short packet"); 345 | let rdlen = BigEndian::read_u16(&packet[offset + 8..]) as usize; 346 | ensure!( 347 | packet_len - offset >= 10 + rdlen, 348 | "Record length would exceed packet length" 349 | ); 350 | cb(packet, offset)?; 351 | offset += 10; 352 | offset += rdlen; 353 | } 354 | Ok(offset) 355 | } 356 | 357 | pub fn min_ttl(packet: &[u8], min_ttl: u32, max_ttl: u32, failure_ttl: u32) -> Result { 358 | let packet_len = packet.len(); 359 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 360 | ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); 361 | ensure!(qdcount(packet) == 1, "No question"); 362 | let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?; 363 | assert!(offset > DNS_OFFSET_QUESTION); 364 | ensure!(packet_len - offset > 4, "Short packet"); 365 | offset += 4; 366 | let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet)); 367 | let rrcount = ancount as usize + nscount as usize + arcount as usize; 368 | let mut found_min_ttl = if rrcount > 0 { max_ttl } else { failure_ttl }; 369 | 370 | offset = traverse_rrs(packet, offset, rrcount, |offset| { 371 | let qtype = BigEndian::read_u16(&packet[offset..]); 372 | let ttl = BigEndian::read_u32(&packet[offset + 4..]); 373 | if qtype != DNS_TYPE_OPT && ttl < found_min_ttl { 374 | found_min_ttl = ttl; 375 | } 376 | Ok(()) 377 | })?; 378 | if found_min_ttl < min_ttl { 379 | found_min_ttl = min_ttl; 380 | } 381 | ensure!(packet_len == offset, "Garbage after packet"); 382 | Ok(found_min_ttl) 383 | } 384 | 385 | pub fn set_ttl(packet: &mut [u8], ttl: u32) -> Result<(), Error> { 386 | let packet_len = packet.len(); 387 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 388 | ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); 389 | ensure!(qdcount(packet) == 1, "No question"); 390 | let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?; 391 | assert!(offset > DNS_OFFSET_QUESTION); 392 | ensure!(packet_len - offset > 4, "Short packet"); 393 | offset += 4; 394 | let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet)); 395 | let rrcount = ancount as usize + nscount as usize + arcount as usize; 396 | offset = traverse_rrs_mut(packet, offset, rrcount, |packet, offset| { 397 | let qtype = BigEndian::read_u16(&packet[offset..]); 398 | if qtype != DNS_TYPE_OPT { 399 | BigEndian::write_u32(&mut packet[offset + 4..], ttl) 400 | } 401 | Ok(()) 402 | })?; 403 | ensure!(packet_len == offset, "Garbage after packet"); 404 | Ok(()) 405 | } 406 | 407 | fn add_edns_section(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { 408 | let opt_rr: [u8; 11] = [ 409 | 0, 410 | (DNS_TYPE_OPT >> 8) as u8, 411 | DNS_TYPE_OPT as u8, 412 | (max_payload_size >> 8) as u8, 413 | max_payload_size as u8, 414 | 0, 415 | 0, 416 | 0, 417 | 0, 418 | 0, 419 | 0, 420 | ]; 421 | ensure!( 422 | DNS_MAX_PACKET_SIZE - packet.len() >= opt_rr.len(), 423 | "Packet would be too large to add a new record" 424 | ); 425 | arcount_inc(packet)?; 426 | packet.extend(opt_rr); 427 | Ok(()) 428 | } 429 | 430 | pub fn set_edns_max_payload_size(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { 431 | let packet_len = packet.len(); 432 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 433 | ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); 434 | ensure!(qdcount(packet) == 1, "No question"); 435 | let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?; 436 | assert!(offset > DNS_OFFSET_QUESTION); 437 | ensure!(packet_len - offset >= 4, "Short packet"); 438 | offset += 4; 439 | let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet)); 440 | offset = traverse_rrs( 441 | packet, 442 | offset, 443 | ancount as usize + nscount as usize, 444 | |_offset| Ok(()), 445 | )?; 446 | let mut edns_payload_set = false; 447 | traverse_rrs_mut(packet, offset, arcount as _, |packet, offset| { 448 | let qtype = BigEndian::read_u16(&packet[offset..]); 449 | if qtype == DNS_TYPE_OPT { 450 | ensure!(!edns_payload_set, "Duplicate OPT RR found"); 451 | BigEndian::write_u16(&mut packet[offset + 2..], max_payload_size); 452 | edns_payload_set = true; 453 | } 454 | Ok(()) 455 | })?; 456 | if edns_payload_set { 457 | return Ok(()); 458 | } 459 | add_edns_section(packet, max_payload_size)?; 460 | Ok(()) 461 | } 462 | 463 | pub fn serve_certificates<'t>( 464 | client_packet: &[u8], 465 | expected_qname: &str, 466 | dnscrypt_encryption_params_set: impl IntoIterator>, 467 | ) -> Result>, Error> { 468 | ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); 469 | ensure!(qdcount(client_packet) == 1, "No question"); 470 | ensure!( 471 | !is_response(client_packet), 472 | "Question expected, but got a response instead" 473 | ); 474 | let offset = skip_name(client_packet, DNS_HEADER_SIZE)?; 475 | ensure!(client_packet.len() - offset >= 4, "Short packet"); 476 | let qtype = BigEndian::read_u16(&client_packet[offset..]); 477 | let qclass = BigEndian::read_u16(&client_packet[offset + 2..]); 478 | if qtype != DNS_TYPE_TXT || qclass != DNS_CLASS_INET { 479 | return Ok(None); 480 | } 481 | let qname_v = qname(client_packet)?; 482 | let qname = std::str::from_utf8(&qname_v)?; 483 | if !qname.eq_ignore_ascii_case(expected_qname) { 484 | return Ok(None); 485 | } 486 | let mut packet = client_packet[..offset + 4].to_vec(); 487 | an_ns_ar_count_clear(&mut packet); 488 | authoritative_response(&mut packet); 489 | let dnscrypt_encryption_params = dnscrypt_encryption_params_set 490 | .into_iter() 491 | .max_by_key(|x| x.dnscrypt_cert().ts_end()) 492 | .ok_or_else(|| anyhow!("No certificates"))?; 493 | let cert_bin = dnscrypt_encryption_params.dnscrypt_cert().as_bytes(); 494 | ensure!(cert_bin.len() <= 0xff, "Certificate too long"); 495 | ancount_inc(&mut packet)?; 496 | packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; 497 | packet.write_u16::(DNS_TYPE_TXT)?; 498 | packet.write_u16::(DNS_CLASS_INET)?; 499 | packet.write_u32::(DNSCRYPT_CERTS_RENEWAL)?; 500 | packet.write_u16::(1 + cert_bin.len() as u16)?; 501 | packet.write_u8(cert_bin.len() as u8)?; 502 | packet.extend_from_slice(cert_bin); 503 | ensure!(packet.len() < DNS_MAX_PACKET_SIZE, "Packet too large"); 504 | 505 | Ok(Some(packet)) 506 | } 507 | 508 | pub fn serve_truncated_response(client_packet: Vec) -> Result, Error> { 509 | ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); 510 | ensure!(qdcount(&client_packet) == 1, "No question"); 511 | ensure!( 512 | !is_response(&client_packet), 513 | "Question expected, but got a response instead" 514 | ); 515 | let offset = skip_name(&client_packet, DNS_HEADER_SIZE)?; 516 | let mut packet = client_packet; 517 | ensure!(packet.len() - offset >= 4, "Short packet"); 518 | packet.truncate(offset + 4); 519 | an_ns_ar_count_clear(&mut packet); 520 | authoritative_response(&mut packet); 521 | truncate(&mut packet); 522 | Ok(packet) 523 | } 524 | 525 | pub fn qtype_qclass(packet: &[u8]) -> Result<(u16, u16), Error> { 526 | ensure!(packet.len() >= DNS_HEADER_SIZE, "Short packet"); 527 | ensure!(qdcount(packet) == 1, "No question"); 528 | let offset = skip_name(packet, DNS_HEADER_SIZE)?; 529 | ensure!(packet.len() - offset >= 4, "Short packet"); 530 | let qtype = BigEndian::read_u16(&packet[offset..]); 531 | let qclass = BigEndian::read_u16(&packet[offset + 2..]); 532 | Ok((qtype, qclass)) 533 | } 534 | 535 | fn parse_txt_rrdata Result<(), Error>>( 536 | rrdata: &[u8], 537 | mut cb: F, 538 | ) -> Result<(), Error> { 539 | let rrdata_len = rrdata.len(); 540 | let mut offset = 0; 541 | while offset < rrdata_len { 542 | let part_len = rrdata[offset] as usize; 543 | if part_len == 0 { 544 | break; 545 | } 546 | ensure!(rrdata_len - offset > part_len, "Short TXT RR data"); 547 | offset += 1; 548 | let part_bin = &rrdata[offset..offset + part_len]; 549 | let part = std::str::from_utf8(part_bin)?; 550 | cb(part)?; 551 | offset += part_len; 552 | } 553 | Ok(()) 554 | } 555 | 556 | pub fn query_meta(packet: &mut Vec) -> Result, Error> { 557 | let packet_len = packet.len(); 558 | ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); 559 | ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); 560 | ensure!(qdcount(packet) == 1, "No question"); 561 | let mut offset = skip_name(packet, DNS_OFFSET_QUESTION)?; 562 | assert!(offset > DNS_OFFSET_QUESTION); 563 | ensure!(packet_len - offset >= 4, "Short packet"); 564 | offset += 4; 565 | let (ancount, nscount, arcount) = (ancount(packet), nscount(packet), arcount(packet)); 566 | offset = traverse_rrs( 567 | packet, 568 | offset, 569 | ancount as usize + nscount as usize, 570 | |_offset| Ok(()), 571 | )?; 572 | let mut token = None; 573 | traverse_rrs(packet, offset, arcount as _, |mut offset| { 574 | let qtype = BigEndian::read_u16(&packet[offset..]); 575 | let qclass = BigEndian::read_u16(&packet[offset + 2..]); 576 | if qtype != DNS_TYPE_TXT || qclass != DNS_CLASS_INET { 577 | return Ok(()); 578 | } 579 | let len = BigEndian::read_u16(&packet[offset + 8..]) as usize; 580 | offset += 10; 581 | ensure!(packet_len - offset >= len, "Short packet"); 582 | let rrdata = &packet[offset..offset + len]; 583 | parse_txt_rrdata(rrdata, |txt| { 584 | if txt.len() < 7 || !txt.starts_with("token:") { 585 | return Ok(()); 586 | } 587 | ensure!(token.is_none(), "Duplicate token"); 588 | let found_token = &txt[6..]; 589 | let found_token = found_token.to_owned(); 590 | token = Some(found_token); 591 | Ok(()) 592 | })?; 593 | Ok(()) 594 | })?; 595 | if token.is_some() { 596 | arcount_clear(packet)?; 597 | packet.truncate(offset); 598 | } 599 | Ok(token) 600 | } 601 | 602 | pub fn serve_nxdomain_response(client_packet: Vec) -> Result, Error> { 603 | ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); 604 | ensure!(qdcount(&client_packet) == 1, "No question"); 605 | ensure!( 606 | !is_response(&client_packet), 607 | "Question expected, but got a response instead" 608 | ); 609 | let offset = skip_name(&client_packet, DNS_HEADER_SIZE)?; 610 | let mut packet = client_packet; 611 | ensure!(packet.len() - offset >= 4, "Short packet"); 612 | packet.truncate(offset + 4); 613 | an_ns_ar_count_clear(&mut packet); 614 | authoritative_response(&mut packet); 615 | set_rcode_nxdomain(&mut packet); 616 | Ok(packet) 617 | } 618 | 619 | pub fn serve_blocked_response(client_packet: Vec) -> Result, Error> { 620 | ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); 621 | ensure!(qdcount(&client_packet) == 1, "No question"); 622 | ensure!( 623 | !is_response(&client_packet), 624 | "Question expected, but got a response instead" 625 | ); 626 | let offset = skip_name(&client_packet, DNS_HEADER_SIZE)?; 627 | let mut packet = client_packet; 628 | ensure!(packet.len() - offset >= 4, "Short packet"); 629 | packet.truncate(offset + 4); 630 | an_ns_ar_count_clear(&mut packet); 631 | authoritative_response(&mut packet); 632 | let hinfo_cpu = b"Query blocked"; 633 | let hinfo_rdata = b"by the DNS server"; 634 | let rdata_len = 1 + hinfo_cpu.len() + 1 + hinfo_rdata.len(); 635 | ancount_inc(&mut packet)?; 636 | packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; 637 | packet.write_u16::(DNS_TYPE_HINFO)?; 638 | packet.write_u16::(DNS_CLASS_INET)?; 639 | packet.write_u32::(60)?; 640 | packet.write_u16::(rdata_len as _)?; 641 | packet.push(hinfo_cpu.len() as u8); 642 | packet.extend_from_slice(hinfo_cpu); 643 | packet.push(hinfo_rdata.len() as u8); 644 | packet.extend_from_slice(hinfo_rdata); 645 | Ok(packet) 646 | } 647 | 648 | pub fn serve_ip_response(client_packet: Vec, ip: IpAddr, ttl: u32) -> Result, Error> { 649 | ensure!(client_packet.len() >= DNS_HEADER_SIZE, "Short packet"); 650 | ensure!(qdcount(&client_packet) == 1, "No question"); 651 | ensure!( 652 | !is_response(&client_packet), 653 | "Question expected, but got a response instead" 654 | ); 655 | let offset = skip_name(&client_packet, DNS_HEADER_SIZE)?; 656 | let mut packet = client_packet; 657 | ensure!(packet.len() - offset >= 4, "Short packet"); 658 | packet.truncate(offset + 4); 659 | an_ns_ar_count_clear(&mut packet); 660 | authoritative_response(&mut packet); 661 | ancount_inc(&mut packet)?; 662 | packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; 663 | match ip { 664 | IpAddr::V4(ip) => { 665 | packet.write_u16::(DNS_TYPE_A)?; 666 | packet.write_u16::(DNS_CLASS_INET)?; 667 | packet.write_u32::(ttl)?; 668 | packet.write_u16::(4)?; 669 | packet.extend_from_slice(&ip.octets()); 670 | } 671 | IpAddr::V6(ip) => { 672 | packet.write_u16::(DNS_TYPE_AAAA)?; 673 | packet.write_u16::(DNS_CLASS_INET)?; 674 | packet.write_u32::(ttl)?; 675 | packet.write_u16::(16)?; 676 | packet.extend_from_slice(&ip.octets()); 677 | } 678 | }; 679 | Ok(packet) 680 | } 681 | -------------------------------------------------------------------------------- /src/dnscrypt.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use libsodium_sys::*; 4 | use rand::prelude::*; 5 | 6 | use crate::crypto::*; 7 | use crate::dns::*; 8 | use crate::dnscrypt_certs::*; 9 | use crate::errors::*; 10 | 11 | pub const DNSCRYPT_FULL_NONCE_SIZE: usize = 12 | crypto_box_curve25519xchacha20poly1305_NONCEBYTES as usize; 13 | pub const DNSCRYPT_MAC_SIZE: usize = crypto_box_curve25519xchacha20poly1305_MACBYTES as usize; 14 | 15 | pub const DNSCRYPT_QUERY_MAGIC_SIZE: usize = 8; 16 | pub const DNSCRYPT_QUERY_PK_SIZE: usize = 32; 17 | pub const DNSCRYPT_QUERY_NONCE_SIZE: usize = DNSCRYPT_FULL_NONCE_SIZE / 2; 18 | pub const DNSCRYPT_QUERY_HEADER_SIZE: usize = 19 | DNSCRYPT_QUERY_MAGIC_SIZE + DNSCRYPT_QUERY_PK_SIZE + DNSCRYPT_QUERY_NONCE_SIZE; 20 | pub const DNSCRYPT_QUERY_MIN_PADDING_SIZE: usize = 1; 21 | pub const DNSCRYPT_QUERY_MIN_OVERHEAD: usize = 22 | DNSCRYPT_QUERY_HEADER_SIZE + DNSCRYPT_MAC_SIZE + DNSCRYPT_QUERY_MIN_PADDING_SIZE; 23 | 24 | pub const DNSCRYPT_RESPONSE_MAGIC_SIZE: usize = 8; 25 | pub const DNSCRYPT_RESPONSE_MAGIC: [u8; DNSCRYPT_RESPONSE_MAGIC_SIZE] = 26 | [0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38]; 27 | pub const DNSCRYPT_RESPONSE_CERT_PREFIX_OFFSET: usize = 4; 28 | pub const DNSCRYPT_RESPONSE_NONCE_SIZE: usize = DNSCRYPT_FULL_NONCE_SIZE; 29 | pub const DNSCRYPT_RESPONSE_HEADER_SIZE: usize = 30 | DNSCRYPT_RESPONSE_MAGIC_SIZE + DNSCRYPT_RESPONSE_NONCE_SIZE; 31 | pub const DNSCRYPT_RESPONSE_MIN_PADDING_SIZE: usize = 1; 32 | pub const DNSCRYPT_RESPONSE_MIN_OVERHEAD: usize = 33 | DNSCRYPT_RESPONSE_HEADER_SIZE + DNSCRYPT_MAC_SIZE + DNSCRYPT_RESPONSE_MIN_PADDING_SIZE; 34 | 35 | pub const DNSCRYPT_UDP_QUERY_MIN_SIZE: usize = DNSCRYPT_QUERY_MIN_OVERHEAD + DNS_HEADER_SIZE; 36 | pub const DNSCRYPT_UDP_QUERY_MAX_SIZE: usize = DNS_MAX_PACKET_SIZE; 37 | pub const DNSCRYPT_TCP_QUERY_MIN_SIZE: usize = DNSCRYPT_QUERY_MIN_OVERHEAD + DNS_HEADER_SIZE; 38 | pub const DNSCRYPT_TCP_QUERY_MAX_SIZE: usize = DNSCRYPT_QUERY_MIN_OVERHEAD + DNS_MAX_PACKET_SIZE; 39 | 40 | pub const DNSCRYPT_UDP_RESPONSE_MIN_SIZE: usize = DNSCRYPT_RESPONSE_MIN_OVERHEAD + DNS_HEADER_SIZE; 41 | pub const DNSCRYPT_UDP_RESPONSE_MAX_SIZE: usize = DNS_MAX_PACKET_SIZE; 42 | pub const DNSCRYPT_TCP_RESPONSE_MIN_SIZE: usize = DNSCRYPT_RESPONSE_MIN_OVERHEAD + DNS_HEADER_SIZE; 43 | pub const DNSCRYPT_TCP_RESPONSE_MAX_SIZE: usize = 44 | DNSCRYPT_RESPONSE_MIN_OVERHEAD + DNS_MAX_PACKET_SIZE; 45 | 46 | pub fn decrypt( 47 | wrapped_packet: &[u8], 48 | dnscrypt_encryption_params_set: &[Arc], 49 | ) -> Result<(SharedKey, [u8; DNSCRYPT_FULL_NONCE_SIZE], Vec), Error> { 50 | ensure!( 51 | wrapped_packet.len() 52 | >= DNSCRYPT_QUERY_MAGIC_SIZE 53 | + DNSCRYPT_QUERY_PK_SIZE 54 | + DNSCRYPT_QUERY_NONCE_SIZE 55 | + DNS_HEADER_SIZE, 56 | "Short packet" 57 | ); 58 | let client_magic = &wrapped_packet[..DNSCRYPT_QUERY_MAGIC_SIZE]; 59 | let client_pk = &wrapped_packet 60 | [DNSCRYPT_QUERY_MAGIC_SIZE..DNSCRYPT_QUERY_MAGIC_SIZE + DNSCRYPT_QUERY_PK_SIZE]; 61 | let client_nonce = &wrapped_packet[DNSCRYPT_QUERY_MAGIC_SIZE + DNSCRYPT_QUERY_PK_SIZE 62 | ..DNSCRYPT_QUERY_MAGIC_SIZE + DNSCRYPT_QUERY_PK_SIZE + DNSCRYPT_QUERY_NONCE_SIZE]; 63 | let encrypted_packet = &wrapped_packet[DNSCRYPT_QUERY_HEADER_SIZE..]; 64 | 65 | let dnscrypt_encryption_params = dnscrypt_encryption_params_set 66 | .iter() 67 | .find(|p| p.client_magic() == client_magic) 68 | .ok_or_else(|| anyhow!("Client magic not found"))?; 69 | 70 | let mut nonce = [0u8; DNSCRYPT_FULL_NONCE_SIZE]; 71 | nonce[..DNSCRYPT_QUERY_NONCE_SIZE].copy_from_slice(client_nonce); 72 | 73 | let cached_shared_key = { 74 | let mut key_cache = dnscrypt_encryption_params 75 | .key_cache 76 | .as_ref() 77 | .unwrap() 78 | .lock(); 79 | key_cache 80 | .get(client_pk) 81 | .map(|cached_shared_key| (*cached_shared_key).clone()) 82 | }; 83 | let shared_key = match cached_shared_key { 84 | Some(cached_shared_key) => cached_shared_key, 85 | None => { 86 | let shared_key = dnscrypt_encryption_params 87 | .resolver_kp() 88 | .compute_shared_key(client_pk)?; 89 | let mut client_pk_ = [0u8; DNSCRYPT_QUERY_PK_SIZE]; 90 | client_pk_.copy_from_slice(client_pk); 91 | dnscrypt_encryption_params 92 | .key_cache 93 | .as_ref() 94 | .unwrap() 95 | .lock() 96 | .insert(client_pk_, shared_key.clone()); 97 | shared_key 98 | } 99 | }; 100 | let packet = shared_key.decrypt(&nonce, encrypted_packet)?; 101 | rand::rng().fill_bytes(&mut nonce[DNSCRYPT_QUERY_NONCE_SIZE..]); 102 | 103 | Ok((shared_key, nonce, packet)) 104 | } 105 | 106 | pub fn encrypt( 107 | packet: Vec, 108 | shared_key: &SharedKey, 109 | nonce: &[u8; DNSCRYPT_FULL_NONCE_SIZE], 110 | max_packet_size: usize, 111 | ) -> Result, Error> { 112 | let mut wrapped_packet = Vec::with_capacity(DNS_MAX_PACKET_SIZE); 113 | wrapped_packet.extend_from_slice(&DNSCRYPT_RESPONSE_MAGIC); 114 | wrapped_packet.extend_from_slice(nonce); 115 | ensure!( 116 | max_packet_size >= wrapped_packet.len(), 117 | "Max packet size too short" 118 | ); 119 | let max_encrypted_size = max_packet_size - wrapped_packet.len(); 120 | shared_key.encrypt_into( 121 | &mut wrapped_packet, 122 | nonce, 123 | &nonce[..DNSCRYPT_QUERY_NONCE_SIZE], 124 | packet, 125 | max_encrypted_size, 126 | )?; 127 | Ok(wrapped_packet) 128 | } 129 | 130 | pub fn may_be_quic(packet: &[u8]) -> bool { 131 | !packet.is_empty() && ((80..=127).contains(&packet[0]) || (192..=255).contains(&packet[0])) 132 | } 133 | -------------------------------------------------------------------------------- /src/dnscrypt_certs.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::slice; 3 | use std::sync::Arc; 4 | use std::time::SystemTime; 5 | 6 | use byteorder::{BigEndian, ByteOrder}; 7 | use parking_lot::Mutex; 8 | use rand::prelude::*; 9 | use serde_big_array::BigArray; 10 | use sieve_cache::SieveCache; 11 | 12 | use crate::anonymized_dns::*; 13 | use crate::config::*; 14 | use crate::crypto::*; 15 | use crate::dnscrypt::*; 16 | use crate::globals::*; 17 | 18 | pub const DNSCRYPT_CERTS_TTL: u32 = 86400; 19 | pub const DNSCRYPT_CERTS_RENEWAL: u32 = 28800; 20 | 21 | fn now() -> u32 { 22 | SystemTime::now() 23 | .duration_since(SystemTime::UNIX_EPOCH) 24 | .expect("The clock is completely off") 25 | .as_secs() as _ 26 | } 27 | 28 | #[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)] 29 | #[repr(C, packed)] 30 | pub struct DNSCryptCertInner { 31 | resolver_pk: [u8; 32], 32 | client_magic: [u8; 8], 33 | serial: [u8; 4], 34 | ts_start: [u8; 4], 35 | ts_end: [u8; 4], 36 | } 37 | 38 | impl DNSCryptCertInner { 39 | pub fn as_bytes(&self) -> &[u8] { 40 | unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of_val(self)) } 41 | } 42 | } 43 | 44 | #[derive(Educe, Serialize, Deserialize)] 45 | #[educe(Debug, Default, Clone)] 46 | #[repr(C, packed)] 47 | pub struct DNSCryptCert { 48 | cert_magic: [u8; 4], 49 | es_version: [u8; 2], 50 | minor_version: [u8; 2], 51 | #[educe(Debug(ignore), Default = [0u8; 64])] 52 | #[serde(with = "BigArray")] 53 | signature: [u8; 64], 54 | inner: DNSCryptCertInner, 55 | } 56 | 57 | impl DNSCryptCert { 58 | pub fn new(provider_kp: &SignKeyPair, resolver_kp: &CryptKeyPair, ts_start: u32) -> Self { 59 | let ts_end = ts_start + DNSCRYPT_CERTS_TTL; 60 | 61 | let mut dnscrypt_cert = DNSCryptCert::default(); 62 | 63 | let dnscrypt_cert_inner = &mut dnscrypt_cert.inner; 64 | dnscrypt_cert_inner 65 | .resolver_pk 66 | .copy_from_slice(resolver_kp.pk.as_bytes()); 67 | dnscrypt_cert_inner 68 | .client_magic 69 | .copy_from_slice(&dnscrypt_cert_inner.resolver_pk[..8]); 70 | BigEndian::write_u32(&mut dnscrypt_cert_inner.serial, 1); 71 | BigEndian::write_u32(&mut dnscrypt_cert_inner.ts_start, ts_start); 72 | BigEndian::write_u32(&mut dnscrypt_cert_inner.ts_end, ts_end); 73 | 74 | BigEndian::write_u32(&mut dnscrypt_cert.cert_magic, 0x44_4e_53_43); 75 | BigEndian::write_u16(&mut dnscrypt_cert.es_version, 2); 76 | BigEndian::write_u16(&mut dnscrypt_cert.minor_version, 0); 77 | 78 | dnscrypt_cert.signature.copy_from_slice( 79 | provider_kp 80 | .sk 81 | .sign(dnscrypt_cert_inner.as_bytes()) 82 | .as_bytes(), 83 | ); 84 | dnscrypt_cert 85 | } 86 | 87 | pub fn as_bytes(&self) -> &[u8] { 88 | unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of_val(self)) } 89 | } 90 | 91 | pub fn client_magic(&self) -> &[u8] { 92 | &self.inner.client_magic 93 | } 94 | 95 | pub fn ts_start(&self) -> u32 { 96 | BigEndian::read_u32(&self.inner.ts_start) 97 | } 98 | 99 | pub fn ts_end(&self) -> u32 { 100 | BigEndian::read_u32(&self.inner.ts_end) 101 | } 102 | } 103 | 104 | #[derive(Serialize, Deserialize, Clone, Educe)] 105 | #[educe(Debug)] 106 | pub struct DNSCryptEncryptionParams { 107 | dnscrypt_cert: DNSCryptCert, 108 | resolver_kp: CryptKeyPair, 109 | #[serde(skip)] 110 | #[educe(Debug(ignore))] 111 | pub key_cache: Option>>>, 112 | } 113 | 114 | impl DNSCryptEncryptionParams { 115 | pub fn new( 116 | provider_kp: &SignKeyPair, 117 | key_cache_capacity: usize, 118 | previous_params: Option>, 119 | ) -> Vec { 120 | let now = now(); 121 | let (mut ts_start, mut seed) = match &previous_params { 122 | None => (now, rand::rng().random()), 123 | Some(p) => ( 124 | p.dnscrypt_cert().ts_start() + DNSCRYPT_CERTS_RENEWAL, 125 | *p.resolver_kp().sk.as_bytes(), 126 | ), 127 | }; 128 | let mut active_params = vec![]; 129 | loop { 130 | if ts_start > now + DNSCRYPT_CERTS_RENEWAL { 131 | break; 132 | } 133 | let resolver_kp = CryptKeyPair::from_seed(seed); 134 | seed = *resolver_kp.sk.as_bytes(); 135 | if resolver_kp.pk.as_bytes() 136 | == &ANONYMIZED_DNSCRYPT_QUERY_MAGIC[..DNSCRYPT_QUERY_MAGIC_SIZE] 137 | { 138 | ts_start += DNSCRYPT_CERTS_RENEWAL; 139 | continue; 140 | } 141 | if now >= ts_start { 142 | let dnscrypt_cert = DNSCryptCert::new(provider_kp, &resolver_kp, ts_start); 143 | let cache = SieveCache::new(key_cache_capacity).unwrap(); 144 | active_params.push(DNSCryptEncryptionParams { 145 | dnscrypt_cert, 146 | resolver_kp, 147 | key_cache: Some(Arc::new(Mutex::new(cache))), 148 | }); 149 | } 150 | ts_start += DNSCRYPT_CERTS_RENEWAL; 151 | } 152 | if active_params.is_empty() && previous_params.is_none() { 153 | warn!("Unable to recover a seed; creating an emergency certificate"); 154 | let ts_start = now - (now % DNSCRYPT_CERTS_RENEWAL); 155 | let resolver_kp = CryptKeyPair::from_seed(seed); 156 | let dnscrypt_cert = DNSCryptCert::new(provider_kp, &resolver_kp, ts_start); 157 | let cache = SieveCache::new(key_cache_capacity).unwrap(); 158 | active_params.push(DNSCryptEncryptionParams { 159 | dnscrypt_cert, 160 | resolver_kp, 161 | key_cache: Some(Arc::new(Mutex::new(cache))), 162 | }); 163 | } 164 | active_params 165 | } 166 | 167 | pub fn add_key_cache(&mut self, cache_capacity: usize) { 168 | let cache = SieveCache::new(cache_capacity).unwrap(); 169 | self.key_cache = Some(Arc::new(Mutex::new(cache))); 170 | } 171 | 172 | pub fn client_magic(&self) -> &[u8] { 173 | self.dnscrypt_cert.client_magic() 174 | } 175 | 176 | pub fn dnscrypt_cert(&self) -> &DNSCryptCert { 177 | &self.dnscrypt_cert 178 | } 179 | 180 | pub fn resolver_kp(&self) -> &CryptKeyPair { 181 | &self.resolver_kp 182 | } 183 | } 184 | 185 | pub struct DNSCryptEncryptionParamsUpdater { 186 | globals: Arc, 187 | } 188 | 189 | impl DNSCryptEncryptionParamsUpdater { 190 | pub fn new(globals: Arc) -> Self { 191 | DNSCryptEncryptionParamsUpdater { globals } 192 | } 193 | 194 | pub fn update(&self) { 195 | let now = now(); 196 | let mut new_params_set = vec![]; 197 | let previous_params = { 198 | let params_set = self.globals.dnscrypt_encryption_params_set.read(); 199 | for params in &**params_set { 200 | if params.dnscrypt_cert().ts_end() >= now { 201 | new_params_set.push(params.clone()); 202 | } 203 | } 204 | params_set.last().cloned() 205 | }; 206 | let active_params = DNSCryptEncryptionParams::new( 207 | &self.globals.provider_kp, 208 | self.globals.key_cache_capacity, 209 | previous_params, 210 | ); 211 | for params in active_params { 212 | new_params_set.push(Arc::new(params)); 213 | } 214 | let state = State { 215 | provider_kp: self.globals.provider_kp.clone(), 216 | dnscrypt_encryption_params_set: new_params_set.iter().map(|x| (**x).clone()).collect(), 217 | }; 218 | let state_file = self.globals.state_file.to_path_buf(); 219 | self.globals.runtime_handle.spawn(async move { 220 | let _ = state.async_save(state_file).await; 221 | }); 222 | *self.globals.dnscrypt_encryption_params_set.write() = Arc::new(new_params_set); 223 | debug!("New certificate issued"); 224 | } 225 | 226 | pub async fn run(self) { 227 | let mut fut_interval = tokio::time::interval(std::time::Duration::from_secs(u64::from( 228 | DNSCRYPT_CERTS_RENEWAL, 229 | ))); 230 | let fut = async move { 231 | loop { 232 | fut_interval.tick().await; 233 | self.update(); 234 | debug!("New cert issued"); 235 | } 236 | }; 237 | fut.await 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | pub use anyhow::{anyhow, bail, ensure, Error}; 2 | -------------------------------------------------------------------------------- /src/globals.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, SocketAddr}; 2 | use std::path::PathBuf; 3 | use std::sync::atomic::AtomicU32; 4 | use std::sync::Arc; 5 | use std::time::Duration; 6 | 7 | use parking_lot::{Mutex, RwLock}; 8 | use siphasher::sip128::SipHasher13; 9 | use slabigator::Slab; 10 | use tokio::runtime::Handle; 11 | use tokio::sync::oneshot; 12 | 13 | use crate::blacklist::*; 14 | use crate::cache::*; 15 | use crate::crypto::*; 16 | use crate::dnscrypt_certs::*; 17 | #[cfg(feature = "metrics")] 18 | use crate::varz::*; 19 | 20 | #[derive(Clone, Educe)] 21 | #[educe(Debug)] 22 | pub struct Globals { 23 | pub runtime_handle: Handle, 24 | pub state_file: PathBuf, 25 | pub dnscrypt_encryption_params_set: Arc>>>>, 26 | pub provider_name: String, 27 | pub provider_kp: SignKeyPair, 28 | pub listen_addrs: Vec, 29 | pub external_addr: Option, 30 | pub upstream_addr: SocketAddr, 31 | pub tls_upstream_addr: Option, 32 | pub udp_timeout: Duration, 33 | pub tcp_timeout: Duration, 34 | pub udp_concurrent_connections: Arc, 35 | pub tcp_concurrent_connections: Arc, 36 | pub udp_max_active_connections: u32, 37 | pub tcp_max_active_connections: u32, 38 | pub udp_active_connections: Arc>>>, 39 | pub tcp_active_connections: Arc>>>, 40 | pub key_cache_capacity: usize, 41 | pub hasher: SipHasher13, 42 | pub cache: Cache, 43 | pub cert_cache: Cache, 44 | pub blacklist: Option, 45 | pub undelegated_list: Option, 46 | pub ignore_unqualified_hostnames: bool, 47 | pub dnscrypt_enabled: bool, 48 | pub anonymized_dns_enabled: bool, 49 | pub anonymized_dns_allowed_ports: Vec, 50 | pub anonymized_dns_allow_non_reserved_ports: bool, 51 | pub anonymized_dns_blacklisted_ips: Vec, 52 | pub access_control_tokens: Option>, 53 | pub client_ttl_holdon: u32, 54 | pub my_ip: Option>, 55 | #[cfg(feature = "metrics")] 56 | #[educe(Debug(ignore))] 57 | pub varz: Varz, 58 | } 59 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::assertions_on_constants)] 2 | #![allow(clippy::type_complexity)] 3 | #![allow(clippy::cognitive_complexity)] 4 | #![allow(clippy::upper_case_acronyms)] 5 | #![allow(clippy::unnecessary_wraps)] 6 | #![allow(clippy::field_reassign_with_default)] 7 | #![allow(dead_code)] 8 | 9 | #[global_allocator] 10 | static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; 11 | 12 | #[macro_use] 13 | extern crate educe; 14 | #[macro_use] 15 | extern crate log; 16 | #[macro_use] 17 | extern crate serde_derive; 18 | #[cfg(feature = "metrics")] 19 | #[macro_use] 20 | extern crate prometheus; 21 | 22 | mod anonymized_dns; 23 | mod blacklist; 24 | mod cache; 25 | mod config; 26 | mod crypto; 27 | mod dns; 28 | mod dnscrypt; 29 | mod dnscrypt_certs; 30 | mod errors; 31 | mod globals; 32 | #[cfg(feature = "metrics")] 33 | mod metrics; 34 | mod resolver; 35 | #[cfg(feature = "metrics")] 36 | mod varz; 37 | 38 | use std::convert::TryFrom; 39 | use std::fs::File; 40 | use std::io::prelude::*; 41 | use std::net::SocketAddr; 42 | use std::path::Path; 43 | use std::sync::atomic::{AtomicU32, Ordering}; 44 | use std::sync::Arc; 45 | use std::time::Duration; 46 | 47 | use anonymized_dns::*; 48 | use blacklist::*; 49 | use byteorder::{BigEndian, ByteOrder}; 50 | use cache::*; 51 | use clap::Arg; 52 | use config::*; 53 | use crypto::*; 54 | use dns::*; 55 | use dnscrypt::*; 56 | use dnscrypt_certs::*; 57 | use dnsstamps::{InformalProperty, WithInformalProperty}; 58 | use errors::*; 59 | use future::Either; 60 | use futures::join; 61 | use futures::prelude::*; 62 | use globals::*; 63 | use parking_lot::Mutex; 64 | use parking_lot::RwLock; 65 | #[cfg(target_family = "unix")] 66 | use privdrop::PrivDrop; 67 | use rand::prelude::*; 68 | use sieve_cache::SieveCache; 69 | use siphasher::sip128::SipHasher13; 70 | use slabigator::Slab; 71 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 72 | use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket}; 73 | use tokio::runtime::Handle; 74 | use tokio::sync::oneshot; 75 | #[cfg(feature = "metrics")] 76 | use varz::*; 77 | 78 | const TCP_BACKLOG: i32 = 1024; 79 | 80 | #[derive(Debug)] 81 | pub struct UdpClientCtx { 82 | net_udp_socket: std::net::UdpSocket, 83 | client_addr: SocketAddr, 84 | } 85 | 86 | #[derive(Debug)] 87 | pub struct TcpClientCtx { 88 | client_connection: TcpStream, 89 | } 90 | 91 | #[derive(Debug)] 92 | pub enum ClientCtx { 93 | Udp(UdpClientCtx), 94 | Tcp(TcpClientCtx), 95 | } 96 | 97 | fn maybe_truncate_response( 98 | client_ctx: &ClientCtx, 99 | packet: Vec, 100 | response: Vec, 101 | original_packet_size: usize, 102 | ) -> Result, Error> { 103 | if let ClientCtx::Udp(_) = client_ctx { 104 | let encrypted_response_min_len = response.len() + DNSCRYPT_RESPONSE_MIN_OVERHEAD; 105 | if encrypted_response_min_len > original_packet_size 106 | || encrypted_response_min_len > DNSCRYPT_UDP_RESPONSE_MAX_SIZE 107 | { 108 | return dns::serve_truncated_response(packet); 109 | } 110 | } 111 | Ok(response) 112 | } 113 | 114 | pub async fn respond_to_query(client_ctx: ClientCtx, response: Vec) -> Result<(), Error> { 115 | match client_ctx { 116 | ClientCtx::Udp(client_ctx) => { 117 | let net_udp_socket = client_ctx.net_udp_socket; 118 | net_udp_socket.send_to(&response, client_ctx.client_addr)?; 119 | } 120 | ClientCtx::Tcp(client_ctx) => { 121 | let response_len = response.len(); 122 | ensure!( 123 | response_len <= DNSCRYPT_TCP_RESPONSE_MAX_SIZE, 124 | "Packet too large" 125 | ); 126 | let mut client_connection = client_ctx.client_connection; 127 | let mut binlen = [0u8, 0]; 128 | BigEndian::write_u16(&mut binlen[..], response_len as u16); 129 | client_connection.write_all(&binlen).await?; 130 | client_connection.write_all(&response).await?; 131 | client_connection.flush().await?; 132 | } 133 | } 134 | Ok(()) 135 | } 136 | 137 | async fn encrypt_and_respond_to_query( 138 | globals: Arc, 139 | client_ctx: ClientCtx, 140 | packet: Vec, 141 | response: Vec, 142 | original_packet_size: usize, 143 | shared_key: Option, 144 | nonce: Option<[u8; DNSCRYPT_FULL_NONCE_SIZE]>, 145 | ) -> Result<(), Error> { 146 | ensure!(dns::is_response(&response), "Packet is not a response"); 147 | let max_response_size = match client_ctx { 148 | ClientCtx::Udp(_) => original_packet_size, 149 | ClientCtx::Tcp(_) => DNSCRYPT_TCP_RESPONSE_MAX_SIZE, 150 | }; 151 | let response = match (&shared_key, &nonce) { 152 | (None, _) => response, 153 | (Some(_), None) => { 154 | warn!("Shared key provided without nonce"); 155 | bail!("Internal error: shared key without nonce"); 156 | }, 157 | (Some(shared_key), Some(nonce)) => dnscrypt::encrypt( 158 | maybe_truncate_response(&client_ctx, packet, response, original_packet_size)?, 159 | shared_key, 160 | nonce, 161 | max_response_size, 162 | )?, 163 | }; 164 | globals.varz.client_queries_resolved.inc(); 165 | if dns::rcode_nxdomain(&response) { 166 | globals.varz.client_queries_rcode_nxdomain.inc(); 167 | } 168 | respond_to_query(client_ctx, response).await 169 | } 170 | 171 | async fn handle_client_query( 172 | globals: Arc, 173 | client_ctx: ClientCtx, 174 | encrypted_packet: Vec, 175 | ) -> Result<(), Error> { 176 | let original_packet_size = encrypted_packet.len(); 177 | ensure!(original_packet_size >= DNS_HEADER_SIZE, "Short packet"); 178 | debug_assert!(DNSCRYPT_QUERY_MIN_OVERHEAD > ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()); 179 | if globals.anonymized_dns_enabled 180 | && original_packet_size >= ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len() + DNS_HEADER_SIZE 181 | && encrypted_packet[..ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()] 182 | == ANONYMIZED_DNSCRYPT_QUERY_MAGIC 183 | { 184 | return handle_anonymized_dns( 185 | globals, 186 | client_ctx, 187 | &encrypted_packet[ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()..], 188 | ) 189 | .await; 190 | } 191 | if !globals.dnscrypt_enabled { 192 | return Ok(()); 193 | } 194 | let mut dnscrypt_encryption_params_set = vec![]; 195 | for params in &**globals.dnscrypt_encryption_params_set.read() { 196 | dnscrypt_encryption_params_set.push((*params).clone()) 197 | } 198 | let (shared_key, nonce, mut packet) = 199 | match dnscrypt::decrypt(&encrypted_packet, &dnscrypt_encryption_params_set) { 200 | Ok(x) => x, 201 | Err(_) => { 202 | let packet = encrypted_packet; 203 | match serve_certificates( 204 | &packet, 205 | &globals.provider_name, 206 | &dnscrypt_encryption_params_set, 207 | ) { 208 | Ok(Some(synth_packet)) => { 209 | return encrypt_and_respond_to_query( 210 | globals, 211 | client_ctx, 212 | packet, 213 | synth_packet, 214 | original_packet_size, 215 | None, 216 | None, 217 | ) 218 | .await 219 | } 220 | Ok(None) => return Ok(()), 221 | Err(_) => { 222 | if may_be_quic(&packet) { 223 | bail!("Likely a QUIC packet") // RFC 9443 224 | } 225 | bail!("Unencrypted query or different protocol") 226 | } 227 | }; 228 | } 229 | }; 230 | ensure!(packet.len() >= DNS_HEADER_SIZE, "Short packet"); 231 | ensure!(qdcount(&packet) == 1, "No question"); 232 | ensure!( 233 | !dns::is_response(&packet), 234 | "Question expected, but got a response instead" 235 | ); 236 | if let Some(tokens) = &globals.access_control_tokens { 237 | match query_meta(&mut packet)? { 238 | None => bail!("No access token"), 239 | Some(token) => ensure!(tokens.contains(&token), "Access token not found"), 240 | } 241 | } 242 | let response = 243 | resolver::get_cached_response_or_resolve(&globals, &client_ctx, &mut packet).await?; 244 | encrypt_and_respond_to_query( 245 | globals, 246 | client_ctx, 247 | packet, 248 | response, 249 | original_packet_size, 250 | Some(shared_key), 251 | Some(nonce), 252 | ) 253 | .await 254 | } 255 | 256 | async fn tls_proxy( 257 | globals: Arc, 258 | binlen: [u8; 2], 259 | mut client_connection: TcpStream, 260 | ) -> Result<(), Error> { 261 | let tls_upstream_addr = match &globals.tls_upstream_addr { 262 | None => return Ok(()), 263 | Some(tls_upstream_addr) => tls_upstream_addr, 264 | }; 265 | let socket = match globals.external_addr { 266 | Some(x @ SocketAddr::V4(_)) => { 267 | let socket = TcpSocket::new_v4()?; 268 | socket.set_reuseaddr(true).ok(); 269 | socket.bind(x)?; 270 | socket 271 | } 272 | Some(x @ SocketAddr::V6(_)) => { 273 | let socket = TcpSocket::new_v6()?; 274 | socket.set_reuseaddr(true).ok(); 275 | socket.bind(x)?; 276 | socket 277 | } 278 | None => match tls_upstream_addr { 279 | SocketAddr::V4(_) => TcpSocket::new_v4()?, 280 | SocketAddr::V6(_) => TcpSocket::new_v6()?, 281 | }, 282 | }; 283 | let mut ext_socket = socket.connect(*tls_upstream_addr).await?; 284 | let (mut erh, mut ewh) = ext_socket.split(); 285 | let (mut rh, mut wh) = client_connection.split(); 286 | ewh.write_all(&binlen).await?; 287 | let fut_proxy_1 = tokio::io::copy(&mut rh, &mut ewh); 288 | let fut_proxy_2 = tokio::io::copy(&mut erh, &mut wh); 289 | match join!(fut_proxy_1, fut_proxy_2) { 290 | (Ok(_), Ok(_)) => Ok(()), 291 | _ => bail!("TLS proxy error"), 292 | } 293 | } 294 | 295 | async fn tcp_acceptor(globals: Arc, tcp_listener: TcpListener) -> Result<(), Error> { 296 | let runtime_handle = globals.runtime_handle.clone(); 297 | let timeout = globals.tcp_timeout; 298 | let concurrent_connections = globals.tcp_concurrent_connections.clone(); 299 | let active_connections = globals.tcp_active_connections.clone(); 300 | loop { 301 | let (mut client_connection, _client_addr) = match tcp_listener.accept().await { 302 | Ok(x) => x, 303 | Err(e) => { 304 | if e.kind() == std::io::ErrorKind::WouldBlock { 305 | continue; 306 | } 307 | error!("TCP accept error: {}", e); 308 | 309 | // Rate limit repeated errors to avoid spinning 310 | let is_resource_error = matches!( 311 | e.kind(), 312 | std::io::ErrorKind::ConnectionRefused | 313 | std::io::ErrorKind::ConnectionReset | 314 | std::io::ErrorKind::ConnectionAborted | 315 | std::io::ErrorKind::AddrInUse | 316 | std::io::ErrorKind::AddrNotAvailable 317 | ); 318 | 319 | if is_resource_error { 320 | // For resource-related errors, try to free up connections 321 | let mut connections = active_connections.lock(); 322 | let freed = connections.len().min(5); 323 | for _ in 0..freed { 324 | if let Some(tx_oldest) = connections.pop_back() { 325 | let _ = tx_oldest.send(()); 326 | } 327 | } 328 | info!("Freed {} connections to recover from resource error", freed); 329 | } 330 | 331 | // Add delay to prevent CPU spinning on persistent errors 332 | tokio::time::sleep(Duration::from_secs(1)).await; 333 | continue; 334 | } 335 | }; 336 | let (tx, rx) = oneshot::channel::<()>(); 337 | let tx_channel_index = { 338 | let mut active_connections = active_connections.lock(); 339 | if active_connections.is_full() { 340 | let tx_oldest = active_connections.pop_back().unwrap(); 341 | let _ = tx_oldest.send(()); 342 | } 343 | active_connections.push_front(tx)? 344 | }; 345 | let _count = concurrent_connections.fetch_add(1, Ordering::Relaxed); 346 | #[cfg(feature = "metrics")] 347 | let varz = globals.varz.clone(); 348 | #[cfg(feature = "metrics")] 349 | { 350 | varz.inflight_tcp_queries.set(_count.saturating_add(1) as _); 351 | varz.client_queries_tcp.inc(); 352 | } 353 | client_connection.set_nodelay(true)?; 354 | let globals = globals.clone(); 355 | let active_connections = active_connections.clone(); 356 | let concurrent_connections = concurrent_connections.clone(); 357 | let fut = async { 358 | let mut binlen = [0u8, 0]; 359 | client_connection.read_exact(&mut binlen).await?; 360 | let packet_len = BigEndian::read_u16(&binlen) as usize; 361 | if packet_len == 0x1603 { 362 | return tls_proxy(globals, binlen, client_connection).await; 363 | } 364 | ensure!( 365 | (DNS_HEADER_SIZE..=DNSCRYPT_TCP_QUERY_MAX_SIZE).contains(&packet_len), 366 | "Unexpected TCP query size" 367 | ); 368 | let mut packet = vec![0u8; packet_len]; 369 | client_connection.read_exact(&mut packet).await?; 370 | let client_ctx = ClientCtx::Tcp(TcpClientCtx { client_connection }); 371 | let _ = handle_client_query(globals, client_ctx, packet).await; 372 | Ok(()) 373 | }; 374 | let fut_abort = rx; 375 | let fut_all = tokio::time::timeout(timeout, future::select(fut.boxed(), fut_abort)); 376 | runtime_handle.spawn(fut_all.map(move |either| { 377 | let _count = concurrent_connections.fetch_sub(1, Ordering::Relaxed); 378 | #[cfg(feature = "metrics")] 379 | varz.inflight_tcp_queries.set(_count.saturating_sub(1) as _); 380 | 381 | if let Ok(Either::Right(e)) = either { 382 | // Removing the active connection was already done during 383 | // cancellation. 384 | debug!("TCP query canceled: {:?}", e.0) 385 | } else { 386 | let mut active_connections = active_connections.lock(); 387 | _ = active_connections.remove(tx_channel_index); 388 | if let Ok(Either::Left(e)) = either { 389 | debug!("TCP query error: {:?}", e.0) 390 | } 391 | } 392 | })); 393 | } 394 | } 395 | 396 | #[allow(unreachable_code)] 397 | async fn udp_acceptor( 398 | globals: Arc, 399 | net_udp_socket: std::net::UdpSocket, 400 | ) -> Result<(), Error> { 401 | let runtime_handle = globals.runtime_handle.clone(); 402 | let tokio_udp_socket = UdpSocket::try_from(net_udp_socket.try_clone()?)?; 403 | let timeout = globals.udp_timeout; 404 | let concurrent_connections = globals.udp_concurrent_connections.clone(); 405 | let active_connections = globals.udp_active_connections.clone(); 406 | loop { 407 | let mut packet = vec![0u8; DNSCRYPT_UDP_QUERY_MAX_SIZE]; 408 | let (packet_len, client_addr) = tokio_udp_socket.recv_from(&mut packet).await?; 409 | if packet_len < DNS_HEADER_SIZE { 410 | continue; 411 | } 412 | // Create a socket clone only when we've checked the packet is valid 413 | // This helps avoid resource exhaustion 414 | packet.truncate(packet_len); 415 | 416 | // Only create a new socket if there's capacity for a new connection 417 | let active_count = concurrent_connections.load(Ordering::Relaxed); 418 | if active_count >= globals.udp_max_active_connections { 419 | debug!("UDP connection limit reached, dropping packet"); 420 | continue; 421 | } 422 | 423 | // Clone the socket for this request 424 | let net_udp_socket = match net_udp_socket.try_clone() { 425 | Ok(socket) => socket, 426 | Err(e) => { 427 | error!("Failed to clone UDP socket: {}", e); 428 | // Add a small delay to avoid spinning on socket errors 429 | tokio::time::sleep(Duration::from_millis(100)).await; 430 | continue; 431 | } 432 | }; 433 | 434 | let client_ctx = ClientCtx::Udp(UdpClientCtx { 435 | net_udp_socket, 436 | client_addr, 437 | }); 438 | let (tx, rx) = oneshot::channel::<()>(); 439 | let tx_channel_index = { 440 | let mut active_connections = active_connections.lock(); 441 | if active_connections.is_full() { 442 | let tx_oldest = active_connections.pop_back().unwrap(); 443 | let _ = tx_oldest.send(()); 444 | } 445 | active_connections.push_front(tx)? 446 | }; 447 | let _count = concurrent_connections.fetch_add(1, Ordering::Relaxed); 448 | #[cfg(feature = "metrics")] 449 | let varz = globals.varz.clone(); 450 | #[cfg(feature = "metrics")] 451 | { 452 | varz.inflight_udp_queries.set(_count.saturating_add(1) as _); 453 | varz.client_queries_udp.inc(); 454 | } 455 | let globals = globals.clone(); 456 | let active_connections = active_connections.clone(); 457 | let concurrent_connections = concurrent_connections.clone(); 458 | let fut = handle_client_query(globals, client_ctx, packet); 459 | let fut_abort = rx; 460 | let fut_all = tokio::time::timeout(timeout, future::select(fut.boxed(), fut_abort)); 461 | runtime_handle.spawn(fut_all.map(move |either| { 462 | let _count = concurrent_connections.fetch_sub(1, Ordering::Relaxed); 463 | #[cfg(feature = "metrics")] 464 | varz.inflight_udp_queries.set(_count.saturating_sub(1) as _); 465 | 466 | if let Ok(Either::Right(e)) = either { 467 | // Removing the active connection was already done during 468 | // cancellation. 469 | debug!("UDP query canceled: {:?}", e.0) 470 | } else { 471 | let mut active_connections = active_connections.lock(); 472 | _ = active_connections.remove(tx_channel_index); 473 | if let Ok(Either::Left(e)) = either { 474 | debug!("UDP query error: {:?}", e.0) 475 | } 476 | } 477 | })); 478 | } 479 | } 480 | 481 | async fn start( 482 | globals: Arc, 483 | runtime_handle: Handle, 484 | listeners: Vec<(std::net::TcpListener, std::net::UdpSocket)>, 485 | ) -> Result<(), Error> { 486 | for listener in listeners { 487 | let tcp_listener_str = format!("{:?}", listener.0); 488 | let tokio_tcp_listener = match TcpListener::from_std(listener.0) { 489 | Ok(tcp_listener) => tcp_listener, 490 | Err(e) => bail!("{}/TCP: {}", tcp_listener_str, e), 491 | }; 492 | runtime_handle.spawn(tcp_acceptor(globals.clone(), tokio_tcp_listener).map(|_| {})); 493 | runtime_handle.spawn(udp_acceptor(globals.clone(), listener.1).map(|_| {})); 494 | } 495 | Ok(()) 496 | } 497 | 498 | fn bind_listeners( 499 | listen_addrs: &[SocketAddr], 500 | ) -> Result, Error> { 501 | let mut sockets = Vec::with_capacity(listen_addrs.len()); 502 | for listen_addr in listen_addrs { 503 | let tcp_listener: std::net::TcpListener = match listen_addr { 504 | SocketAddr::V4(_) => { 505 | let kindy = socket2::Socket::new( 506 | socket2::Domain::IPV4, 507 | socket2::Type::STREAM, 508 | Some(socket2::Protocol::TCP), 509 | )?; 510 | kindy.set_reuse_address(true)?; 511 | kindy.bind(&(*listen_addr).into())?; 512 | kindy.listen(TCP_BACKLOG as _)?; 513 | kindy.into() 514 | } 515 | SocketAddr::V6(_) => { 516 | let kindy = socket2::Socket::new( 517 | socket2::Domain::IPV6, 518 | socket2::Type::STREAM, 519 | Some(socket2::Protocol::TCP), 520 | )?; 521 | kindy.set_reuse_address(true)?; 522 | kindy.set_only_v6(true)?; 523 | kindy.bind(&(*listen_addr).into())?; 524 | kindy.listen(TCP_BACKLOG as _)?; 525 | kindy.into() 526 | } 527 | }; 528 | tcp_listener.set_nonblocking(true)?; 529 | let udp_socket: std::net::UdpSocket = match listen_addr { 530 | SocketAddr::V4(_) => { 531 | let kindy = socket2::Socket::new( 532 | socket2::Domain::IPV4, 533 | socket2::Type::DGRAM, 534 | Some(socket2::Protocol::UDP), 535 | )?; 536 | kindy.set_reuse_address(true)?; 537 | kindy.bind(&(*listen_addr).into())?; 538 | kindy.into() 539 | } 540 | SocketAddr::V6(_) => { 541 | let kindy = socket2::Socket::new( 542 | socket2::Domain::IPV6, 543 | socket2::Type::DGRAM, 544 | Some(socket2::Protocol::UDP), 545 | )?; 546 | kindy.set_reuse_address(true)?; 547 | kindy.set_only_v6(true)?; 548 | kindy.bind(&(*listen_addr).into())?; 549 | kindy.into() 550 | } 551 | }; 552 | udp_socket.set_nonblocking(true)?; 553 | sockets.push((tcp_listener, udp_socket)) 554 | } 555 | Ok(sockets) 556 | } 557 | 558 | #[cfg(target_family = "unix")] 559 | fn privdrop(config: &Config) -> Result<(), Error> { 560 | let mut pd = PrivDrop::default(); 561 | if let Some(user) = &config.user { 562 | pd = pd.user(user); 563 | } 564 | if let Some(group) = &config.group { 565 | pd = pd.group(group); 566 | } 567 | if let Some(chroot) = &config.chroot { 568 | if !config.daemonize { 569 | pd = pd.chroot(chroot); 570 | } 571 | } 572 | if config.user.is_some() || config.group.is_some() || config.chroot.is_some() { 573 | info!("Dropping privileges"); 574 | pd.apply()?; 575 | } 576 | if config.daemonize { 577 | let mut daemon = daemonize_simple::Daemonize::default(); 578 | daemon.stdout_file.clone_from(&config.log_file); 579 | daemon.stderr_file.clone_from(&config.log_file); 580 | daemon.pid_file.clone_from(&config.pid_file); 581 | if let Some(chroot) = &config.chroot { 582 | daemon.chdir = Some(chroot.into()); 583 | daemon.chroot = true; 584 | } 585 | daemon 586 | .doit() 587 | .map_err(|e| anyhow!("Unable to daemonize: [{}]", e))?; 588 | } 589 | Ok(()) 590 | } 591 | 592 | #[cfg(not(target_family = "unix"))] 593 | fn set_limits(_config: &Config) -> Result<(), Error> { 594 | Ok(()) 595 | } 596 | 597 | #[cfg(target_family = "unix")] 598 | fn set_limits(config: &Config) -> Result<(), Error> { 599 | use rlimit::Resource; 600 | let nb_descriptors = 4u32 601 | .saturating_mul( 602 | config 603 | .tcp_max_active_connections 604 | .saturating_add(config.udp_max_active_connections) 605 | .saturating_add(config.listen_addrs.len() as u32), 606 | ) 607 | .saturating_add(16); 608 | let (_soft, hard) = Resource::NOFILE.get()?; 609 | if nb_descriptors as u64 > hard as u64 { 610 | warn!( 611 | "Unable to set the number of open files to {}. The hard limit is {}", 612 | nb_descriptors, hard 613 | ); 614 | } 615 | let nb_descriptors = std::cmp::max(nb_descriptors as _, hard); 616 | Resource::NOFILE.set(nb_descriptors, nb_descriptors)?; 617 | Ok(()) 618 | } 619 | 620 | fn main() -> Result<(), Error> { 621 | let matches = clap::command!() 622 | .arg( 623 | Arg::new("config") 624 | .long("config") 625 | .short('c') 626 | .value_name("file") 627 | .num_args(1) 628 | .default_value("encrypted-dns.toml") 629 | .help("Path to the configuration file"), 630 | ) 631 | .arg( 632 | Arg::new("import-from-dnscrypt-wrapper") 633 | .long("import-from-dnscrypt-wrapper") 634 | .value_name("secret.key file") 635 | .num_args(1) 636 | .help("Path to the dnscrypt-wrapper secret key"), 637 | ) 638 | .arg( 639 | Arg::new("dry-run") 640 | .long("dry-run") 641 | .num_args(0) 642 | .help("Only print the connection information and quit"), 643 | ) 644 | .arg( 645 | Arg::new("debug") 646 | .long("debug") 647 | .num_args(0) 648 | .help("Enable debug logs"), 649 | ) 650 | .get_matches(); 651 | 652 | let log_level = if matches.get_flag("debug") { 653 | log::LevelFilter::Debug 654 | } else { 655 | log::LevelFilter::Info 656 | }; 657 | 658 | env_logger::Builder::from_default_env() 659 | .write_style(env_logger::WriteStyle::Never) 660 | .format_module_path(false) 661 | .format_timestamp(None) 662 | .filter_level(log_level) 663 | .target(env_logger::Target::Stdout) 664 | .init(); 665 | 666 | crypto::init()?; 667 | let time_updater = coarsetime::Updater::new(1000).start()?; 668 | 669 | let config_path = matches.get_one::("config").unwrap(); 670 | let config = Config::from_path(config_path)?; 671 | if let Err(e) = set_limits(&config) { 672 | warn!("Unable to set limits: [{}]", e); 673 | } 674 | let dnscrypt_enabled = config.dnscrypt.enabled.unwrap_or(true); 675 | let provider_name = match &config.dnscrypt.provider_name { 676 | provider_name if provider_name.starts_with("2.dnscrypt-cert.") => provider_name.to_string(), 677 | provider_name => format!("2.dnscrypt-cert.{}", provider_name), 678 | }; 679 | let external_addr = config.external_addr.map(|addr| SocketAddr::new(addr, 0)); 680 | 681 | let listen_addrs: Vec<_> = config.listen_addrs.iter().map(|x| x.local).collect(); 682 | let listeners = bind_listeners(&listen_addrs) 683 | .map_err(|e| { 684 | error!("Unable to listen to the requested IPs and ports: [{}]", e); 685 | std::process::exit(1); 686 | }) 687 | .unwrap(); 688 | 689 | let mut runtime_builder = tokio::runtime::Builder::new_multi_thread(); 690 | runtime_builder.enable_all(); 691 | runtime_builder.thread_name("encrypted-dns-"); 692 | let runtime = runtime_builder.build()?; 693 | 694 | #[cfg(target_family = "unix")] 695 | privdrop(&config)?; 696 | 697 | let key_cache_capacity = config.dnscrypt.key_cache_capacity; 698 | let cache_capacity = config.cache_capacity; 699 | let state_file = &config.state_file; 700 | 701 | if let Some(secret_key_path) = matches.get_one::("import-from-dnscrypt-wrapper") { 702 | let secret_key_path = Path::new(secret_key_path); 703 | warn!("Importing dnscrypt-wrapper key"); 704 | let mut key = vec![]; 705 | File::open(secret_key_path)?.read_to_end(&mut key)?; 706 | if key.len() != 64 { 707 | bail!("Key doesn't have the expected size"); 708 | } 709 | let mut sign_sk_u8 = [0u8; 64]; 710 | let mut sign_pk_u8 = [0u8; 32]; 711 | sign_sk_u8.copy_from_slice(&key); 712 | sign_pk_u8.copy_from_slice(&key[32..]); 713 | let provider_kp = SignKeyPair { 714 | sk: SignSK::from_bytes(sign_sk_u8), 715 | pk: SignPK::from_bytes(sign_pk_u8), 716 | }; 717 | runtime.block_on( 718 | State::with_key_pair(provider_kp, key_cache_capacity).async_save(state_file), 719 | )?; 720 | warn!("Key successfully imported"); 721 | } 722 | 723 | let (state, state_is_new) = match State::from_file(state_file, key_cache_capacity) { 724 | Err(_) => { 725 | warn!("No state file found... creating a new provider key"); 726 | let state = State::new(key_cache_capacity); 727 | runtime.block_on(state.async_save(state_file))?; 728 | (state, true) 729 | } 730 | Ok(state) => { 731 | info!( 732 | "State file [{}] found; using existing provider key", 733 | state_file.as_os_str().to_string_lossy() 734 | ); 735 | (state, false) 736 | } 737 | }; 738 | let provider_kp = state.provider_kp; 739 | for listen_addr_s in &config.listen_addrs { 740 | info!("Public server address: {}", listen_addr_s.external); 741 | info!("Provider public key: {}", provider_kp.pk.as_string()); 742 | info!("Provider name: {}", provider_name); 743 | let mut stamp = dnsstamps::DNSCryptBuilder::new(dnsstamps::DNSCryptProvider::new( 744 | provider_name.clone(), 745 | provider_kp.pk.as_bytes().to_vec(), 746 | )) 747 | .with_addr(listen_addr_s.external.to_string()); 748 | if config.dnscrypt.dnssec { 749 | stamp = stamp.with_informal_property(InformalProperty::DNSSEC); 750 | } 751 | if config.dnscrypt.no_filters { 752 | stamp = stamp.with_informal_property(InformalProperty::NoFilters); 753 | } 754 | if config.dnscrypt.no_logs { 755 | stamp = stamp.with_informal_property(InformalProperty::NoLogs); 756 | } 757 | let stamp = stamp.serialize().unwrap(); 758 | info!("DNS Stamp: {}", stamp); 759 | 760 | if let Some(anonymized_dns) = &config.anonymized_dns { 761 | if anonymized_dns.enabled { 762 | let relay_stamp = dnsstamps::DNSCryptRelayBuilder::new() 763 | .with_addr(listen_addr_s.external.to_string()) 764 | .serialize() 765 | .unwrap(); 766 | info!("DNS Stamp for Anonymized DNS relaying: {}", relay_stamp); 767 | } 768 | } 769 | } 770 | if matches.get_flag("dry-run") { 771 | return Ok(()); 772 | } 773 | let dnscrypt_encryption_params_set = state 774 | .dnscrypt_encryption_params_set 775 | .into_iter() 776 | .map(Arc::new) 777 | .collect::>(); 778 | 779 | let (sh_k0, sh_k1) = rand::rng().random(); 780 | let hasher = SipHasher13::new_with_keys(sh_k0, sh_k1); 781 | 782 | let cache = Cache::new( 783 | SieveCache::new(cache_capacity) 784 | .map_err(|e| anyhow!("Unable to create the DNS cache: [{}]", e))?, 785 | config.cache_ttl_min, 786 | config.cache_ttl_max, 787 | config.cache_ttl_error, 788 | ); 789 | let cert_cache = Cache::new( 790 | SieveCache::new(RELAYED_CERT_CACHE_SIZE) 791 | .map_err(|e| anyhow!("Unable to create the relay cert cache: [{}]", e))?, 792 | RELAYED_CERT_CACHE_TTL, 793 | RELAYED_CERT_CACHE_TTL, 794 | RELAYED_CERT_CACHE_TTL, 795 | ); 796 | let blacklist = match config.filtering.domain_blacklist { 797 | None => None, 798 | Some(path) => Some( 799 | BlackList::load(&path) 800 | .map_err(|e| anyhow!("Unable to load the blacklist [{:?}]: [{}]", path, e))?, 801 | ), 802 | }; 803 | let undelegated_list = match config.filtering.undelegated_list { 804 | None => None, 805 | Some(path) => Some(BlackList::load(&path).map_err(|e| { 806 | anyhow!( 807 | "Unable to load the list of undelegated TLDs [{:?}]: [{}]", 808 | path, 809 | e 810 | ) 811 | })?), 812 | }; 813 | let ignore_unqualified_hostnames = config 814 | .filtering 815 | .ignore_unqualified_hostnames 816 | .unwrap_or(true); 817 | let ( 818 | anonymized_dns_enabled, 819 | anonymized_dns_allowed_ports, 820 | anonymized_dns_allow_non_reserved_ports, 821 | anonymized_dns_blacklisted_ips, 822 | ) = match config.anonymized_dns { 823 | None => (false, vec![], false, vec![]), 824 | Some(anonymized_dns) => ( 825 | anonymized_dns.enabled, 826 | anonymized_dns.allowed_ports, 827 | anonymized_dns.allow_non_reserved_ports.unwrap_or(false), 828 | anonymized_dns.blacklisted_ips, 829 | ), 830 | }; 831 | let access_control_tokens = match config.access_control { 832 | Some(access_control) if access_control.enabled && !access_control.tokens.is_empty() => { 833 | info!("Access control enabled"); 834 | Some(access_control.tokens) 835 | } 836 | _ => None, 837 | }; 838 | let runtime_handle = runtime.handle(); 839 | let globals = Arc::new(Globals { 840 | runtime_handle: runtime_handle.clone(), 841 | state_file: state_file.to_path_buf(), 842 | dnscrypt_encryption_params_set: Arc::new(RwLock::new(Arc::new( 843 | dnscrypt_encryption_params_set, 844 | ))), 845 | provider_name, 846 | provider_kp, 847 | listen_addrs, 848 | upstream_addr: config.upstream_addr, 849 | tls_upstream_addr: config.tls.upstream_addr, 850 | external_addr, 851 | tcp_timeout: Duration::from_secs(u64::from(config.tcp_timeout)), 852 | udp_timeout: Duration::from_secs(u64::from(config.udp_timeout)), 853 | udp_concurrent_connections: Arc::new(AtomicU32::new(0)), 854 | tcp_concurrent_connections: Arc::new(AtomicU32::new(0)), 855 | udp_max_active_connections: config.udp_max_active_connections, 856 | tcp_max_active_connections: config.tcp_max_active_connections, 857 | udp_active_connections: Arc::new(Mutex::new(Slab::with_capacity( 858 | config.udp_max_active_connections as _, 859 | )?)), 860 | tcp_active_connections: Arc::new(Mutex::new(Slab::with_capacity( 861 | config.tcp_max_active_connections as _, 862 | )?)), 863 | key_cache_capacity, 864 | hasher, 865 | cache, 866 | cert_cache, 867 | blacklist, 868 | undelegated_list, 869 | ignore_unqualified_hostnames, 870 | dnscrypt_enabled, 871 | anonymized_dns_enabled, 872 | anonymized_dns_allowed_ports, 873 | anonymized_dns_allow_non_reserved_ports, 874 | anonymized_dns_blacklisted_ips, 875 | access_control_tokens, 876 | my_ip: config.my_ip.map(|ip| ip.as_bytes().to_ascii_lowercase()), 877 | client_ttl_holdon: config.client_ttl_holdon.unwrap_or(60), 878 | #[cfg(feature = "metrics")] 879 | varz: Varz::default(), 880 | }); 881 | let updater = DNSCryptEncryptionParamsUpdater::new(globals.clone()); 882 | if !state_is_new { 883 | updater.update(); 884 | } 885 | #[cfg(feature = "metrics")] 886 | { 887 | if let Some(metrics_config) = config.metrics { 888 | runtime_handle.spawn( 889 | metrics::prometheus_service( 890 | globals.varz.clone(), 891 | metrics_config, 892 | runtime_handle.clone(), 893 | ) 894 | .map_err(|e| { 895 | error!("Unable to start the metrics service: [{}]", e); 896 | std::process::exit(1); 897 | }) 898 | .map(|_| ()), 899 | ); 900 | } 901 | } 902 | runtime_handle.spawn( 903 | start(globals, runtime_handle.clone(), listeners) 904 | .map_err(|e| { 905 | error!("Unable to start the service: [{}]", e); 906 | std::process::exit(1); 907 | }) 908 | .map(|_| ()), 909 | ); 910 | runtime.block_on(updater.run()); 911 | time_updater.stop()?; 912 | Ok(()) 913 | } 914 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU32, Ordering}; 2 | use std::sync::Arc; 3 | 4 | #[allow(unused_imports)] 5 | use futures::prelude::*; 6 | use hyper::header::CONTENT_TYPE; 7 | use hyper::server::conn::http1; 8 | use hyper::service::service_fn; 9 | use hyper::{Request, Response, StatusCode}; 10 | use hyper_util::rt::TokioIo; 11 | use prometheus::{self, Encoder, TextEncoder}; 12 | use tokio::net::TcpListener; 13 | use tokio::runtime::Handle; 14 | 15 | use crate::config::*; 16 | use crate::errors::*; 17 | use crate::varz::*; 18 | 19 | const METRICS_CONNECTION_TIMEOUT_SECS: u64 = 10; 20 | const METRICS_MAX_CONCURRENT_CONNECTIONS: u32 = 2; 21 | 22 | type BoxBody = http_body_util::Full; 23 | 24 | async fn handle_client_connection( 25 | req: Request, 26 | _varz: Varz, 27 | path: Arc, 28 | ) -> Result, Error> { 29 | if req.uri().path() != path.as_str() { 30 | return Ok(Response::builder().status(StatusCode::NOT_FOUND).body( 31 | http_body_util::Full::new(hyper::body::Bytes::from("404 Not Found")), 32 | )?); 33 | } 34 | let encoder = TextEncoder::new(); 35 | let metric_families = prometheus::gather(); 36 | let mut buffer = vec![]; 37 | encoder.encode(&metric_families, &mut buffer)?; 38 | Ok(Response::builder() 39 | .status(StatusCode::OK) 40 | .header(CONTENT_TYPE, encoder.format_type()) 41 | .body(http_body_util::Full::new(hyper::body::Bytes::from(buffer)))?) 42 | } 43 | 44 | pub async fn prometheus_service( 45 | varz: Varz, 46 | metrics_config: MetricsConfig, 47 | runtime_handle: Handle, 48 | ) -> Result<(), Error> { 49 | let listener = TcpListener::bind(metrics_config.listen_addr).await?; 50 | let path = Arc::new(metrics_config.path); 51 | let connection_count = Arc::new(AtomicU32::new(0)); 52 | 53 | loop { 54 | let (stream, _) = listener.accept().await?; 55 | let varz = varz.clone(); 56 | let path = path.clone(); 57 | let connection_count = Arc::clone(&connection_count); 58 | 59 | if connection_count.load(Ordering::Relaxed) >= METRICS_MAX_CONCURRENT_CONNECTIONS { 60 | continue; 61 | } 62 | connection_count.fetch_add(1, Ordering::Relaxed); 63 | 64 | runtime_handle.spawn(async move { 65 | let io = TokioIo::new(stream); 66 | let _ = http1::Builder::new() 67 | .keep_alive(true) 68 | .serve_connection( 69 | io, 70 | service_fn(move |req| { 71 | handle_client_connection(req, varz.clone(), path.clone()) 72 | }), 73 | ) 74 | .with_upgrades() 75 | .await; 76 | 77 | connection_count.fetch_sub(1, Ordering::Relaxed); 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/resolver.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::hash::Hasher; 3 | use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 4 | 5 | use byteorder::{BigEndian, ByteOrder}; 6 | use rand::{random, Rng, rng}; 7 | use siphasher::sip128::Hasher128; 8 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 9 | use tokio::net::{TcpSocket, UdpSocket}; 10 | 11 | use crate::cache::*; 12 | use crate::dns::{self, *}; 13 | use crate::errors::*; 14 | use crate::globals::*; 15 | use crate::ClientCtx; 16 | 17 | pub async fn resolve_udp( 18 | globals: &Globals, 19 | packet: &mut Vec, 20 | packet_qname: &[u8], 21 | tid: u16, 22 | has_cached_response: bool, 23 | ) -> Result, Error> { 24 | let ext_socket = match globals.external_addr { 25 | Some(x) => UdpSocket::bind(x).await?, 26 | None => match globals.upstream_addr { 27 | SocketAddr::V4(_) => { 28 | UdpSocket::bind(&SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))) 29 | .await? 30 | } 31 | SocketAddr::V6(s) => { 32 | UdpSocket::bind(&SocketAddr::V6(SocketAddrV6::new( 33 | Ipv6Addr::UNSPECIFIED, 34 | 0, 35 | s.flowinfo(), 36 | s.scope_id(), 37 | ))) 38 | .await? 39 | } 40 | }, 41 | }; 42 | ext_socket.connect(globals.upstream_addr).await?; 43 | dns::set_edns_max_payload_size(packet, DNS_MAX_PACKET_SIZE as u16)?; 44 | let mut response; 45 | let timeout = if has_cached_response { 46 | globals.udp_timeout / 2 47 | } else { 48 | globals.udp_timeout 49 | }; 50 | loop { 51 | ext_socket.send(packet).await?; 52 | response = vec![0u8; DNS_MAX_PACKET_SIZE]; 53 | dns::set_rcode_servfail(&mut response); 54 | let fut = tokio::time::timeout(timeout, ext_socket.recv_from(&mut response[..])); 55 | match fut.await { 56 | Ok(Ok((response_len, response_addr))) => { 57 | response.truncate(response_len); 58 | if response_addr == globals.upstream_addr 59 | && response_len >= DNS_HEADER_SIZE 60 | && dns::tid(&response) == tid 61 | && packet_qname.eq_ignore_ascii_case(dns::qname(&response)?.as_slice()) 62 | { 63 | break; 64 | } 65 | } 66 | _ => { 67 | if has_cached_response { 68 | trace!("Timeout, but cached response is present"); 69 | break; 70 | } 71 | trace!("Timeout, no cached response"); 72 | } 73 | } 74 | } 75 | Ok(response) 76 | } 77 | 78 | pub async fn resolve_tcp( 79 | globals: &Globals, 80 | packet: &mut [u8], 81 | packet_qname: &[u8], 82 | tid: u16, 83 | ) -> Result, Error> { 84 | let socket = match globals.external_addr { 85 | Some(x @ SocketAddr::V4(_)) => { 86 | let socket = TcpSocket::new_v4()?; 87 | socket.set_reuseaddr(true).ok(); 88 | socket.bind(x)?; 89 | socket 90 | } 91 | Some(x @ SocketAddr::V6(_)) => { 92 | let socket = TcpSocket::new_v6()?; 93 | socket.set_reuseaddr(true).ok(); 94 | socket.bind(x)?; 95 | socket 96 | } 97 | None => match globals.upstream_addr { 98 | SocketAddr::V4(_) => TcpSocket::new_v4()?, 99 | SocketAddr::V6(_) => TcpSocket::new_v6()?, 100 | }, 101 | }; 102 | let mut ext_socket = socket.connect(globals.upstream_addr).await?; 103 | ext_socket.set_nodelay(true)?; 104 | let mut binlen = [0u8, 0]; 105 | BigEndian::write_u16(&mut binlen[..], packet.len() as u16); 106 | ext_socket.write_all(&binlen).await?; 107 | ext_socket.write_all(packet).await?; 108 | ext_socket.flush().await?; 109 | ext_socket.read_exact(&mut binlen).await?; 110 | let response_len = BigEndian::read_u16(&binlen) as usize; 111 | ensure!( 112 | (DNS_HEADER_SIZE..=DNS_MAX_PACKET_SIZE).contains(&response_len), 113 | "Unexpected response size" 114 | ); 115 | let mut response = vec![0u8; response_len]; 116 | ext_socket.read_exact(&mut response).await?; 117 | ensure!(dns::tid(&response) == tid, "Unexpected transaction ID"); 118 | ensure!( 119 | packet_qname.eq_ignore_ascii_case(dns::qname(&response)?.as_slice()), 120 | "Unexpected query name in the response" 121 | ); 122 | Ok(response) 123 | } 124 | 125 | pub async fn resolve( 126 | globals: &Globals, 127 | packet: &mut Vec, 128 | packet_qname: Vec, 129 | cached_response: Option, 130 | packet_hash: u128, 131 | original_tid: u16, 132 | ) -> Result, Error> { 133 | #[cfg(feature = "metrics")] 134 | globals.varz.upstream_sent.inc(); 135 | let tid = random(); 136 | dns::set_tid(packet, tid); 137 | let mut response = resolve_udp( 138 | globals, 139 | packet, 140 | &packet_qname, 141 | tid, 142 | cached_response.is_some(), 143 | ) 144 | .await?; 145 | if dns::is_truncated(&response) { 146 | response = resolve_tcp(globals, packet, &packet_qname, tid).await?; 147 | } 148 | #[cfg(feature = "metrics")] 149 | { 150 | globals.varz.upstream_received.inc(); 151 | if dns::rcode_nxdomain(&response) { 152 | globals.varz.upstream_rcode_nxdomain.inc(); 153 | } 154 | } 155 | if dns::rcode_servfail(&response) || dns::rcode_refused(&response) { 156 | trace!("SERVFAIL/REFUSED: {}", dns::rcode(&response)); 157 | if let Some(cached_response) = cached_response { 158 | trace!("Serving stale"); 159 | #[cfg(feature = "metrics")] 160 | { 161 | globals.varz.client_queries_offline.inc(); 162 | globals.varz.client_queries_cached.inc(); 163 | } 164 | return Ok(cached_response.into_response()); 165 | } else { 166 | #[cfg(feature = "metrics")] 167 | globals.varz.upstream_errors.inc(); 168 | } 169 | } else { 170 | trace!("Adding to cache"); 171 | let cached_response = CachedResponse::new(&globals.cache, response.clone()); 172 | globals.cache.lock().insert(packet_hash, cached_response); 173 | } 174 | dns::set_tid(&mut response, original_tid); 175 | dns::recase_qname(&mut response, &packet_qname)?; 176 | #[cfg(feature = "metrics")] 177 | globals 178 | .varz 179 | .upstream_response_sizes 180 | .observe(response.len() as f64); 181 | Ok(response) 182 | } 183 | 184 | pub async fn get_cached_response_or_resolve( 185 | globals: &Globals, 186 | client_ctx: &ClientCtx, 187 | packet: &mut Vec, 188 | ) -> Result, Error> { 189 | let packet_qname = dns::qname(packet)?; 190 | if let Some(my_ip) = &globals.my_ip { 191 | if &packet_qname.to_ascii_lowercase() == my_ip { 192 | let client_ip = match client_ctx { 193 | ClientCtx::Udp(u) => u.client_addr, 194 | ClientCtx::Tcp(t) => t.client_connection.peer_addr()?, 195 | } 196 | .ip(); 197 | return serve_ip_response(packet.to_vec(), client_ip, 1); 198 | } 199 | } 200 | if let Some(blacklist) = &globals.blacklist { 201 | if blacklist.find(&packet_qname) { 202 | #[cfg(feature = "metrics")] 203 | globals.varz.client_queries_blocked.inc(); 204 | return dns::serve_blocked_response(packet.to_vec()); 205 | } 206 | } 207 | let tld = dns::qname_tld(&packet_qname); 208 | let synthesize_nxdomain = { 209 | if globals.ignore_unqualified_hostnames && tld.len() == packet_qname.len() { 210 | let (qtype, qclass) = dns::qtype_qclass(packet)?; 211 | qtype == dns::DNS_CLASS_INET 212 | && (qclass == dns::DNS_TYPE_A || qclass == dns::DNS_TYPE_AAAA) 213 | } else if let Some(undelegated_list) = &globals.undelegated_list { 214 | undelegated_list.find(tld) 215 | } else { 216 | false 217 | } 218 | }; 219 | if synthesize_nxdomain { 220 | #[cfg(feature = "metrics")] 221 | globals.varz.client_queries_rcode_nxdomain.inc(); 222 | return dns::serve_nxdomain_response(packet.to_vec()); 223 | } 224 | let original_tid = dns::tid(packet); 225 | dns::set_tid(packet, 0); 226 | dns::normalize_qname(packet)?; 227 | // Create a new hasher instance to avoid race conditions 228 | let (sh_k0, sh_k1) = rng().random::<(u64, u64)>(); 229 | let mut hasher = siphasher::sip128::SipHasher13::new_with_keys(sh_k0, sh_k1); 230 | hasher.write(packet); 231 | let packet_hash = hasher.finish128().as_u128(); 232 | let cached_response = { 233 | match globals.cache.lock().get(&packet_hash) { 234 | None => None, 235 | Some(response) => { 236 | let cached_response = (*response).clone(); 237 | Some(cached_response) 238 | } 239 | } 240 | }; 241 | let cached_response = match cached_response { 242 | None => None, 243 | Some(mut cached_response) => { 244 | if !cached_response.has_expired() { 245 | trace!("Cached"); 246 | #[cfg(feature = "metrics")] 247 | globals.varz.client_queries_cached.inc(); 248 | cached_response.set_tid(original_tid); 249 | let original_ttl = cached_response.original_ttl(); 250 | let mut ttl = cached_response.ttl(); 251 | if ttl.saturating_add(globals.client_ttl_holdon) > original_ttl { 252 | ttl = original_ttl; 253 | } 254 | ttl = cmp::max(1, ttl); 255 | let mut response = cached_response.into_response(); 256 | dns::set_ttl(&mut response, ttl)?; 257 | dns::recase_qname(&mut response, &packet_qname)?; 258 | return Ok(response); 259 | } 260 | trace!("Expired"); 261 | #[cfg(feature = "metrics")] 262 | globals.varz.client_queries_expired.inc(); 263 | Some(cached_response) 264 | } 265 | }; 266 | resolve( 267 | globals, 268 | packet, 269 | packet_qname, 270 | cached_response, 271 | packet_hash, 272 | original_tid, 273 | ) 274 | .await 275 | } 276 | -------------------------------------------------------------------------------- /src/varz.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use coarsetime::Instant; 4 | use prometheus::{Histogram, IntCounter, IntGauge}; 5 | 6 | pub struct StartInstant(pub Instant); 7 | 8 | pub struct Inner { 9 | pub start_instant: StartInstant, 10 | pub uptime: IntGauge, 11 | pub anonymized_queries: IntCounter, 12 | pub anonymized_responses: IntCounter, 13 | pub client_queries: IntGauge, 14 | pub client_queries_udp: IntCounter, 15 | pub client_queries_tcp: IntCounter, 16 | pub client_queries_cached: IntCounter, 17 | pub client_queries_expired: IntCounter, 18 | pub client_queries_offline: IntCounter, 19 | pub client_queries_errors: IntCounter, 20 | pub client_queries_blocked: IntCounter, 21 | pub client_queries_resolved: IntCounter, 22 | pub client_queries_rcode_nxdomain: IntCounter, 23 | pub inflight_udp_queries: IntGauge, 24 | pub inflight_tcp_queries: IntGauge, 25 | pub upstream_errors: IntCounter, 26 | pub upstream_sent: IntCounter, 27 | pub upstream_received: IntCounter, 28 | pub upstream_response_sizes: Histogram, 29 | pub upstream_rcode_nxdomain: IntCounter, 30 | } 31 | 32 | pub type Varz = Arc; 33 | 34 | pub fn new() -> Varz { 35 | Arc::new(Inner::new()) 36 | } 37 | 38 | impl Inner { 39 | pub fn new() -> Inner { 40 | Inner { 41 | start_instant: StartInstant::default(), 42 | uptime: register_int_gauge!(opts!( 43 | "encrypted_dns_uptime", 44 | "Uptime", 45 | labels! {"handler" => "all",} 46 | )) 47 | .unwrap(), 48 | anonymized_queries: register_int_counter!(opts!( 49 | "encrypted_dns_anonymized_queries", 50 | "Number of anonymized queries received", 51 | labels! {"handler" => "all",} 52 | )) 53 | .unwrap(), 54 | anonymized_responses: register_int_counter!(opts!( 55 | "encrypted_dns_anonymized_responses", 56 | "Number of anonymized responses received", 57 | labels! {"handler" => "all",} 58 | )) 59 | .unwrap(), 60 | client_queries: register_int_gauge!(opts!( 61 | "encrypted_dns_client_queries", 62 | "Number of client queries received", 63 | labels! {"handler" => "all",} 64 | )) 65 | .unwrap(), 66 | client_queries_udp: register_int_counter!(opts!( 67 | "encrypted_dns_client_queries_udp", 68 | "Number of client queries received using UDP", 69 | labels! {"handler" => "all",} 70 | )) 71 | .unwrap(), 72 | client_queries_tcp: register_int_counter!(opts!( 73 | "encrypted_dns_client_queries_tcp", 74 | "Number of client queries received using TCP", 75 | labels! {"handler" => "all",} 76 | )) 77 | .unwrap(), 78 | client_queries_cached: register_int_counter!(opts!( 79 | "encrypted_dns_client_queries_cached", 80 | "Number of client queries sent from the cache", 81 | labels! {"handler" => "all",} 82 | )) 83 | .unwrap(), 84 | client_queries_expired: register_int_counter!(opts!( 85 | "encrypted_dns_client_queries_expired", 86 | "Number of expired client queries", 87 | labels! {"handler" => "all",} 88 | )) 89 | .unwrap(), 90 | client_queries_offline: register_int_counter!(opts!( 91 | "encrypted_dns_client_queries_offline", 92 | "Number of client queries answered while upstream resolvers are unresponsive", 93 | labels! {"handler" => "all",} 94 | )) 95 | .unwrap(), 96 | client_queries_errors: register_int_counter!(opts!( 97 | "encrypted_dns_client_queries_errors", 98 | "Number of bogus client queries", 99 | labels! {"handler" => "all",} 100 | )) 101 | .unwrap(), 102 | client_queries_blocked: register_int_counter!(opts!( 103 | "encrypted_dns_client_queries_blocked", 104 | "Number of blocked client queries", 105 | labels! {"handler" => "all",} 106 | )) 107 | .unwrap(), 108 | client_queries_resolved: register_int_counter!(opts!( 109 | "encrypted_dns_client_queries_resolved", 110 | "Number of blocked client resolved", 111 | labels! {"handler" => "all",} 112 | )) 113 | .unwrap(), 114 | client_queries_rcode_nxdomain: register_int_counter!(opts!( 115 | "encrypted_dns_client_queries_rcode_nxdomain", 116 | "Number of responses with an NXDOMAIN error code", 117 | labels! {"handler" => "all",} 118 | )) 119 | .unwrap(), 120 | inflight_udp_queries: register_int_gauge!(opts!( 121 | "encrypted_dns_inflight_udp_queries", 122 | "Number of UDP queries currently waiting for a response", 123 | labels! {"handler" => "all",} 124 | )) 125 | .unwrap(), 126 | inflight_tcp_queries: register_int_gauge!(opts!( 127 | "encrypted_dns_inflight_tcp_queries", 128 | "Number of TCP queries currently waiting for a response", 129 | labels! {"handler" => "all",} 130 | )) 131 | .unwrap(), 132 | upstream_errors: register_int_counter!(opts!( 133 | "encrypted_dns_upstream_errors", 134 | "Number of bogus upstream servers responses", 135 | labels! {"handler" => "all",} 136 | )) 137 | .unwrap(), 138 | upstream_sent: register_int_counter!(opts!( 139 | "encrypted_dns_upstream_sent", 140 | "Number of upstream servers queries sent", 141 | labels! {"handler" => "all",} 142 | )) 143 | .unwrap(), 144 | upstream_received: register_int_counter!(opts!( 145 | "encrypted_dns_upstream_received", 146 | "Number of upstream servers responses received", 147 | labels! {"handler" => "all",} 148 | )) 149 | .unwrap(), 150 | upstream_response_sizes: register_histogram!(histogram_opts!( 151 | "encrypted_dns_upstream_response_sizes", 152 | "Response size in bytes", 153 | vec![64.0, 128.0, 192.0, 256.0, 512.0, 1024.0, 2048.0] 154 | )) 155 | .unwrap(), 156 | upstream_rcode_nxdomain: register_int_counter!(opts!( 157 | "encrypted_dns_upstream_rcode_nxdomain", 158 | "Number of upstream responses with an NXDOMAIN error code", 159 | labels! {"handler" => "all",} 160 | )) 161 | .unwrap(), 162 | } 163 | } 164 | } 165 | 166 | impl Default for Inner { 167 | fn default() -> Self { 168 | Self::new() 169 | } 170 | } 171 | 172 | impl Default for StartInstant { 173 | fn default() -> StartInstant { 174 | StartInstant(Instant::now()) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /undelegated.txt: -------------------------------------------------------------------------------- 1 | 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa 2 | 0.in-addr.arpa 3 | 1 4 | 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa 5 | 10.in-addr.arpa 6 | 100.100.in-addr.arpa 7 | 100.51.198.in-addr.arpa 8 | 101.100.in-addr.arpa 9 | 102.100.in-addr.arpa 10 | 103.100.in-addr.arpa 11 | 104.100.in-addr.arpa 12 | 105.100.in-addr.arpa 13 | 106.100.in-addr.arpa 14 | 107.100.in-addr.arpa 15 | 108.100.in-addr.arpa 16 | 109.100.in-addr.arpa 17 | 110.100.in-addr.arpa 18 | 111.100.in-addr.arpa 19 | 112.100.in-addr.arpa 20 | 113.0.203.in-addr.arpa 21 | 113.100.in-addr.arpa 22 | 114.100.in-addr.arpa 23 | 115.100.in-addr.arpa 24 | 116.100.in-addr.arpa 25 | 117.100.in-addr.arpa 26 | 118.100.in-addr.arpa 27 | 119.100.in-addr.arpa 28 | 120.100.in-addr.arpa 29 | 121.100.in-addr.arpa 30 | 122.100.in-addr.arpa 31 | 123.100.in-addr.arpa 32 | 124.100.in-addr.arpa 33 | 125.100.in-addr.arpa 34 | 126.100.in-addr.arpa 35 | 127.100.in-addr.arpa 36 | 127.in-addr.arpa 37 | 16.172.in-addr.arpa 38 | 168.192.in-addr.arpa 39 | 17.172.in-addr.arpa 40 | 18.172.in-addr.arpa 41 | 19.172.in-addr.arpa 42 | 2.0.192.in-addr.arpa 43 | 20.172.in-addr.arpa 44 | 21.172.in-addr.arpa 45 | 22.172.in-addr.arpa 46 | 23.172.in-addr.arpa 47 | 24.172.in-addr.arpa 48 | 25.172.in-addr.arpa 49 | 254.169.in-addr.arpa 50 | 255.255.255.255.in-addr.arpa 51 | 26.172.in-addr.arpa 52 | 27.172.in-addr.arpa 53 | 28.172.in-addr.arpa 54 | 29.172.in-addr.arpa 55 | 30.172.in-addr.arpa 56 | 31.172.in-addr.arpa 57 | 64.100.in-addr.arpa 58 | 65.100.in-addr.arpa 59 | 66.100.in-addr.arpa 60 | 67.100.in-addr.arpa 61 | 68.100.in-addr.arpa 62 | 69.100.in-addr.arpa 63 | 70.100.in-addr.arpa 64 | 71.100.in-addr.arpa 65 | 72.100.in-addr.arpa 66 | 73.100.in-addr.arpa 67 | 74.100.in-addr.arpa 68 | 75.100.in-addr.arpa 69 | 76.100.in-addr.arpa 70 | 77.100.in-addr.arpa 71 | 78.100.in-addr.arpa 72 | 79.100.in-addr.arpa 73 | 8.b.d.0.1.0.0.2.ip6.arpa 74 | 8.e.f.ip6.arpa 75 | 80.100.in-addr.arpa 76 | 81.100.in-addr.arpa 77 | 82.100.in-addr.arpa 78 | 83.100.in-addr.arpa 79 | 84.100.in-addr.arpa 80 | 85.100.in-addr.arpa 81 | 86.100.in-addr.arpa 82 | 87.100.in-addr.arpa 83 | 88.100.in-addr.arpa 84 | 89.100.in-addr.arpa 85 | 9.e.f.ip6.arpa 86 | 90.100.in-addr.arpa 87 | 91.100.in-addr.arpa 88 | 92.100.in-addr.arpa 89 | 93.100.in-addr.arpa 90 | 94.100.in-addr.arpa 91 | 95.100.in-addr.arpa 92 | 96.100.in-addr.arpa 93 | 97.100.in-addr.arpa 94 | 98.100.in-addr.arpa 95 | 99.100.in-addr.arpa 96 | a.e.f.ip6.arpa 97 | airdream 98 | api 99 | b.e.f.ip6.arpa 100 | bbrouter 101 | belkin 102 | bind 103 | blinkap 104 | corp 105 | d.f.ip6.arpa 106 | davolink 107 | dearmyrouter 108 | dhcp 109 | dlink 110 | dnscrypt 111 | domain 112 | envoy 113 | example 114 | f.f.ip6.arpa 115 | fritz.box 116 | grp 117 | gw== 118 | home 119 | home.arpa 120 | hub 121 | id.opendns.com 122 | internal 123 | intra 124 | intranet 125 | invalid 126 | ksyun 127 | lan 128 | loc 129 | local 130 | localdomain 131 | localhost 132 | localnet 133 | mail 134 | modem 135 | mynet 136 | myrouter 137 | novalocal 138 | onion 139 | openstacklocal 140 | priv 141 | private 142 | prv 143 | router 144 | server 145 | telus 146 | test 147 | totolink 148 | wlan_ap 149 | workgroup 150 | zghjccbob3n0 151 | --------------------------------------------------------------------------------