├── .dockerignore ├── .editorconfig ├── .github ├── FUNDING.yml ├── dependabot.yml ├── logo.png └── workflows │ ├── audit.yml │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── COMPARISON.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE-MIT ├── README.md ├── assets ├── index.html ├── test_text_stats.txt └── test_text_stats_1_13_2.txt ├── benches └── text_parser.rs ├── fuzz ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── dicts │ └── text_parser.txt └── fuzz_targets │ ├── fuzz_measurement_observe.rs │ └── fuzz_text_parser.rs └── src ├── bin └── server │ ├── cli.rs │ ├── main.rs │ └── server.rs ├── lib.rs ├── macros.rs ├── metrics ├── mod.rs ├── observe.rs └── value.rs ├── sources ├── control │ ├── mod.rs │ ├── text.rs │ ├── tls.rs │ └── uds.rs ├── memory │ ├── ffi.rs │ ├── mod.rs │ ├── shm.rs │ ├── types.rs │ └── wrappers.rs └── mod.rs └── statistics ├── histogram.rs ├── mod.rs └── parser ├── errors.rs ├── mod.rs ├── tests.rs └── types.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | target/ 3 | Dockerfile 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | liberapay: svartalf 2 | patreon: svartalf 3 | custom: ["https://svartalf.info/donate/", "https://www.buymeacoffee.com/svartalf"] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "02:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svartalf/unbound-telemetry/68e31cbc2973ce8406e78cf32d5be7e83c7ea764/.github/logo.png -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 7 * * *' 5 | jobs: 6 | audit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: actions-rs/audit-check@v1 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | tier_1: 7 | name: Tier 1 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macOS-latest 14 | - windows-latest 15 | toolchain: 16 | - 1.46.0 # MSRV 17 | - stable 18 | - nightly 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install Rust toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | profile: minimal 25 | toolchain: ${{ matrix.toolchain }} 26 | override: true 27 | - name: Run build 28 | run: cargo build 29 | 30 | tier_2: 31 | name: Tier 2 32 | # Allowing to fail for a while, because building OpenSSL is a huge pain 33 | continue-on-error: true 34 | runs-on: ubuntu-latest 35 | strategy: 36 | matrix: 37 | target: 38 | - aarch64-unknown-linux-gnu 39 | - armv7-unknown-linux-gnueabihf 40 | - x86_64-unknown-freebsd 41 | - x86_64-unknown-netbsd 42 | # - x86_64-unknown-openbsd 43 | # - x86_64-unknown-dragonfly 44 | toolchain: 45 | - 1.46.0 # MSRV 46 | - stable 47 | - nightly 48 | 49 | steps: 50 | - uses: actions/checkout@v2 51 | - name: Install Rust toolchain 52 | uses: actions-rs/toolchain@v1 53 | with: 54 | profile: minimal 55 | toolchain: ${{ matrix.toolchain }} 56 | override: true 57 | target: ${{ matrix.target }} 58 | - name: Run build 59 | uses: actions-rs/cargo@v1 60 | env: 61 | PKG_CONFIG_ALLOW_CROSS: 1 62 | with: 63 | command: check 64 | args: --target ${{ matrix.target }} --features vendored 65 | use-cross: true 66 | 67 | lints: 68 | name: Lints 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v2 72 | - name: Install nightly toolchain 73 | uses: actions-rs/toolchain@v1 74 | with: 75 | profile: minimal 76 | toolchain: nightly 77 | override: true 78 | components: rustfmt, clippy 79 | - name: Run rustfmt 80 | run: cargo fmt --all -- --check 81 | - name: Run clippy 82 | run: cargo clippy --all-targets --all-features -- -D warnings 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Unix" 2 | use_try_shorthand = true 3 | max_width = 120 4 | -------------------------------------------------------------------------------- /COMPARISON.md: -------------------------------------------------------------------------------- 1 | # Comparison with `kumina/unbound_exporter` 2 | 3 | While this exporter tries its best to mimic the [kumina/unbound_exporter](https://github.com/kumina/unbound_exporter) behavior, 4 | there are few notable differences that should be noted. 5 | 6 | ## Added 7 | 8 | A lot of additional metrics from the `unbound`, see their help annotations for details. 9 | 10 | ### Global metrics 11 | 12 | * `unbound_num_threads` 13 | * `unbound_memory_stream_wait_count` 14 | * `unbound_query_ratelimited_total` 15 | * `unbound_query_tcp_out_total` 16 | * `unbound_query_tls_resume_total` 17 | * `unbound_cache_count_total` 18 | * `unbound_memory_modules_bytes{module="ipsecmod"}` 19 | 20 | ### Per-thread metrics 21 | 22 | * `unbound_dnscrypt_cert_queries_total` 23 | * `unbound_dnscrypt_cleartext_queries_total` 24 | * `unbound_dnscrypt_malformed_queries_total` 25 | * `unbound_dnscrypt_valid_queries_total` 26 | * `unbound_queries_ip_ratelimited_total` 27 | * `unbound_zero_ttl_responses_total` 28 | 29 | ### Technical metrics 30 | 31 | * `unbound_scrape_duration_seconds` 32 | 33 | 34 | ## Deprecated 35 | 36 | * `unbound_answers_bogus`: use `unbound_answers_bogus_total` instead 37 | * `unbound_msg_cache_count`: use `unbound_cache_count_total{type="message"}` instead 38 | * `unbound_rrset_cache_count`: use `unbound_cache_count_total{type="rrset"}` instead 39 | 40 | 41 | ## TODO 42 | 43 | * `unbound_dnscrypt_query_replays_total` 44 | * `unbound_dnscrypt_valid_queries_total` 45 | * `unbound_query_aggressive_total` 46 | * `unbound_query_authzone_down_total` 47 | * `unbound_query_authzone_up_total` 48 | * `unbound_query_subnet_cache_total` 49 | * `unbound_query_subnet_total` 50 | * `unbound_query_tcp_out_total` 51 | * `unbound_query_tls_resume_total` 52 | * `unbound_tcp_usage_current` 53 | * `unbound_zero_ttl_responses_total` 54 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi 0.3.9", 10 | ] 11 | 12 | [[package]] 13 | name = "approx" 14 | version = "0.5.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" 17 | dependencies = [ 18 | "num-traits", 19 | ] 20 | 21 | [[package]] 22 | name = "async-trait" 23 | version = "0.1.51" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" 26 | dependencies = [ 27 | "proc-macro2", 28 | "quote", 29 | "syn", 30 | ] 31 | 32 | [[package]] 33 | name = "atty" 34 | version = "0.2.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 37 | dependencies = [ 38 | "hermit-abi", 39 | "libc", 40 | "winapi 0.3.9", 41 | ] 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 48 | 49 | [[package]] 50 | name = "bitflags" 51 | version = "1.3.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 54 | 55 | [[package]] 56 | name = "bytes" 57 | version = "0.5.6" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 60 | 61 | [[package]] 62 | name = "bytes" 63 | version = "1.1.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 66 | 67 | [[package]] 68 | name = "cc" 69 | version = "1.0.70" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "0.1.10" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 78 | 79 | [[package]] 80 | name = "cfg-if" 81 | version = "1.0.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 84 | 85 | [[package]] 86 | name = "claim" 87 | version = "0.5.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "f81099d6bb72e1df6d50bb2347224b666a670912bb7f06dbe867a4a070ab3ce8" 90 | dependencies = [ 91 | "autocfg", 92 | ] 93 | 94 | [[package]] 95 | name = "clap" 96 | version = "2.33.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 99 | dependencies = [ 100 | "ansi_term", 101 | "atty", 102 | "bitflags", 103 | "strsim", 104 | "textwrap", 105 | "unicode-width", 106 | "vec_map", 107 | ] 108 | 109 | [[package]] 110 | name = "core-foundation" 111 | version = "0.6.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 114 | dependencies = [ 115 | "core-foundation-sys", 116 | "libc", 117 | ] 118 | 119 | [[package]] 120 | name = "core-foundation-sys" 121 | version = "0.6.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 124 | 125 | [[package]] 126 | name = "domain" 127 | version = "0.6.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "2eb073186f6285f852b9e71b544111306ab08da4a6b40c25a73f4c9ee3e3df29" 130 | dependencies = [ 131 | "rand", 132 | ] 133 | 134 | [[package]] 135 | name = "dtoa" 136 | version = "0.4.8" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 139 | 140 | [[package]] 141 | name = "fnv" 142 | version = "1.0.7" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 145 | 146 | [[package]] 147 | name = "foreign-types" 148 | version = "0.3.2" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 151 | dependencies = [ 152 | "foreign-types-shared", 153 | ] 154 | 155 | [[package]] 156 | name = "foreign-types-shared" 157 | version = "0.1.1" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 160 | 161 | [[package]] 162 | name = "fuchsia-zircon" 163 | version = "0.3.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 166 | dependencies = [ 167 | "bitflags", 168 | "fuchsia-zircon-sys", 169 | ] 170 | 171 | [[package]] 172 | name = "fuchsia-zircon-sys" 173 | version = "0.3.3" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 176 | 177 | [[package]] 178 | name = "futures-channel" 179 | version = "0.3.17" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" 182 | dependencies = [ 183 | "futures-core", 184 | ] 185 | 186 | [[package]] 187 | name = "futures-core" 188 | version = "0.3.17" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" 191 | 192 | [[package]] 193 | name = "futures-sink" 194 | version = "0.3.17" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" 197 | 198 | [[package]] 199 | name = "futures-task" 200 | version = "0.3.17" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" 203 | 204 | [[package]] 205 | name = "futures-util" 206 | version = "0.3.17" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" 209 | dependencies = [ 210 | "autocfg", 211 | "futures-core", 212 | "futures-task", 213 | "pin-project-lite 0.2.7", 214 | "pin-utils", 215 | ] 216 | 217 | [[package]] 218 | name = "getrandom" 219 | version = "0.2.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 222 | dependencies = [ 223 | "cfg-if 1.0.0", 224 | "libc", 225 | "wasi", 226 | ] 227 | 228 | [[package]] 229 | name = "h2" 230 | version = "0.2.7" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" 233 | dependencies = [ 234 | "bytes 0.5.6", 235 | "fnv", 236 | "futures-core", 237 | "futures-sink", 238 | "futures-util", 239 | "http", 240 | "indexmap", 241 | "slab", 242 | "tokio", 243 | "tokio-util", 244 | "tracing", 245 | "tracing-futures", 246 | ] 247 | 248 | [[package]] 249 | name = "hashbrown" 250 | version = "0.11.2" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 253 | 254 | [[package]] 255 | name = "heck" 256 | version = "0.3.3" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 259 | dependencies = [ 260 | "unicode-segmentation", 261 | ] 262 | 263 | [[package]] 264 | name = "hermit-abi" 265 | version = "0.1.19" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 268 | dependencies = [ 269 | "libc", 270 | ] 271 | 272 | [[package]] 273 | name = "http" 274 | version = "0.2.4" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" 277 | dependencies = [ 278 | "bytes 1.1.0", 279 | "fnv", 280 | "itoa", 281 | ] 282 | 283 | [[package]] 284 | name = "http-body" 285 | version = "0.3.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 288 | dependencies = [ 289 | "bytes 0.5.6", 290 | "http", 291 | ] 292 | 293 | [[package]] 294 | name = "httparse" 295 | version = "1.5.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" 298 | 299 | [[package]] 300 | name = "httpdate" 301 | version = "0.3.2" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" 304 | 305 | [[package]] 306 | name = "hyper" 307 | version = "0.13.10" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" 310 | dependencies = [ 311 | "bytes 0.5.6", 312 | "futures-channel", 313 | "futures-core", 314 | "futures-util", 315 | "h2", 316 | "http", 317 | "http-body", 318 | "httparse", 319 | "httpdate", 320 | "itoa", 321 | "pin-project", 322 | "socket2", 323 | "tokio", 324 | "tower-service", 325 | "tracing", 326 | "want", 327 | ] 328 | 329 | [[package]] 330 | name = "indexmap" 331 | version = "1.7.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 334 | dependencies = [ 335 | "autocfg", 336 | "hashbrown", 337 | ] 338 | 339 | [[package]] 340 | name = "iovec" 341 | version = "0.1.4" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 344 | dependencies = [ 345 | "libc", 346 | ] 347 | 348 | [[package]] 349 | name = "itoa" 350 | version = "0.4.8" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 353 | 354 | [[package]] 355 | name = "kernel32-sys" 356 | version = "0.2.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 359 | dependencies = [ 360 | "winapi 0.2.8", 361 | "winapi-build", 362 | ] 363 | 364 | [[package]] 365 | name = "lazy_static" 366 | version = "1.4.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 369 | 370 | [[package]] 371 | name = "libc" 372 | version = "0.2.102" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" 375 | 376 | [[package]] 377 | name = "log" 378 | version = "0.4.14" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 381 | dependencies = [ 382 | "cfg-if 1.0.0", 383 | ] 384 | 385 | [[package]] 386 | name = "memchr" 387 | version = "2.4.1" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 390 | 391 | [[package]] 392 | name = "mio" 393 | version = "0.6.23" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 396 | dependencies = [ 397 | "cfg-if 0.1.10", 398 | "fuchsia-zircon", 399 | "fuchsia-zircon-sys", 400 | "iovec", 401 | "kernel32-sys", 402 | "libc", 403 | "log", 404 | "miow", 405 | "net2", 406 | "slab", 407 | "winapi 0.2.8", 408 | ] 409 | 410 | [[package]] 411 | name = "mio-uds" 412 | version = "0.6.8" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 415 | dependencies = [ 416 | "iovec", 417 | "libc", 418 | "mio", 419 | ] 420 | 421 | [[package]] 422 | name = "miow" 423 | version = "0.2.2" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 426 | dependencies = [ 427 | "kernel32-sys", 428 | "net2", 429 | "winapi 0.2.8", 430 | "ws2_32-sys", 431 | ] 432 | 433 | [[package]] 434 | name = "native-tls" 435 | version = "0.2.3" 436 | source = "git+https://github.com/Goirad/rust-native-tls.git?branch=pkcs8-squashed#255dd5493b446755a9e40be3a4638afedfe67b03" 437 | dependencies = [ 438 | "lazy_static", 439 | "libc", 440 | "log", 441 | "openssl", 442 | "openssl-probe", 443 | "openssl-sys", 444 | "schannel", 445 | "security-framework", 446 | "security-framework-sys", 447 | "tempfile", 448 | ] 449 | 450 | [[package]] 451 | name = "net2" 452 | version = "0.2.37" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 455 | dependencies = [ 456 | "cfg-if 0.1.10", 457 | "libc", 458 | "winapi 0.3.9", 459 | ] 460 | 461 | [[package]] 462 | name = "num-traits" 463 | version = "0.2.14" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 466 | dependencies = [ 467 | "autocfg", 468 | ] 469 | 470 | [[package]] 471 | name = "once_cell" 472 | version = "1.8.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 475 | 476 | [[package]] 477 | name = "openssl" 478 | version = "0.10.36" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" 481 | dependencies = [ 482 | "bitflags", 483 | "cfg-if 1.0.0", 484 | "foreign-types", 485 | "libc", 486 | "once_cell", 487 | "openssl-sys", 488 | ] 489 | 490 | [[package]] 491 | name = "openssl-probe" 492 | version = "0.1.4" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" 495 | 496 | [[package]] 497 | name = "openssl-src" 498 | version = "111.16.0+1.1.1l" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "7ab2173f69416cf3ec12debb5823d244127d23a9b127d5a5189aa97c5fa2859f" 501 | dependencies = [ 502 | "cc", 503 | ] 504 | 505 | [[package]] 506 | name = "openssl-sys" 507 | version = "0.9.66" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" 510 | dependencies = [ 511 | "autocfg", 512 | "cc", 513 | "libc", 514 | "openssl-src", 515 | "pkg-config", 516 | "vcpkg", 517 | ] 518 | 519 | [[package]] 520 | name = "pin-project" 521 | version = "1.0.8" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" 524 | dependencies = [ 525 | "pin-project-internal", 526 | ] 527 | 528 | [[package]] 529 | name = "pin-project-internal" 530 | version = "1.0.8" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" 533 | dependencies = [ 534 | "proc-macro2", 535 | "quote", 536 | "syn", 537 | ] 538 | 539 | [[package]] 540 | name = "pin-project-lite" 541 | version = "0.1.12" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" 544 | 545 | [[package]] 546 | name = "pin-project-lite" 547 | version = "0.2.7" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 550 | 551 | [[package]] 552 | name = "pin-utils" 553 | version = "0.1.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 556 | 557 | [[package]] 558 | name = "pkg-config" 559 | version = "0.3.19" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 562 | 563 | [[package]] 564 | name = "ppv-lite86" 565 | version = "0.2.10" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 568 | 569 | [[package]] 570 | name = "proc-macro-error" 571 | version = "1.0.4" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 574 | dependencies = [ 575 | "proc-macro-error-attr", 576 | "proc-macro2", 577 | "quote", 578 | "syn", 579 | "version_check", 580 | ] 581 | 582 | [[package]] 583 | name = "proc-macro-error-attr" 584 | version = "1.0.4" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 587 | dependencies = [ 588 | "proc-macro2", 589 | "quote", 590 | "version_check", 591 | ] 592 | 593 | [[package]] 594 | name = "proc-macro2" 595 | version = "1.0.29" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 598 | dependencies = [ 599 | "unicode-xid", 600 | ] 601 | 602 | [[package]] 603 | name = "quote" 604 | version = "1.0.9" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 607 | dependencies = [ 608 | "proc-macro2", 609 | ] 610 | 611 | [[package]] 612 | name = "rand" 613 | version = "0.8.4" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 616 | dependencies = [ 617 | "libc", 618 | "rand_chacha", 619 | "rand_core", 620 | "rand_hc", 621 | ] 622 | 623 | [[package]] 624 | name = "rand_chacha" 625 | version = "0.3.1" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 628 | dependencies = [ 629 | "ppv-lite86", 630 | "rand_core", 631 | ] 632 | 633 | [[package]] 634 | name = "rand_core" 635 | version = "0.6.3" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 638 | dependencies = [ 639 | "getrandom", 640 | ] 641 | 642 | [[package]] 643 | name = "rand_hc" 644 | version = "0.3.1" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 647 | dependencies = [ 648 | "rand_core", 649 | ] 650 | 651 | [[package]] 652 | name = "redox_syscall" 653 | version = "0.2.10" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 656 | dependencies = [ 657 | "bitflags", 658 | ] 659 | 660 | [[package]] 661 | name = "remove_dir_all" 662 | version = "0.5.3" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 665 | dependencies = [ 666 | "winapi 0.3.9", 667 | ] 668 | 669 | [[package]] 670 | name = "schannel" 671 | version = "0.1.19" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 674 | dependencies = [ 675 | "lazy_static", 676 | "winapi 0.3.9", 677 | ] 678 | 679 | [[package]] 680 | name = "security-framework" 681 | version = "0.3.4" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" 684 | dependencies = [ 685 | "core-foundation", 686 | "core-foundation-sys", 687 | "libc", 688 | "security-framework-sys", 689 | ] 690 | 691 | [[package]] 692 | name = "security-framework-sys" 693 | version = "0.3.3" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" 696 | dependencies = [ 697 | "core-foundation-sys", 698 | ] 699 | 700 | [[package]] 701 | name = "signal-hook-registry" 702 | version = "1.4.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 705 | dependencies = [ 706 | "libc", 707 | ] 708 | 709 | [[package]] 710 | name = "simple_logger" 711 | version = "1.13.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "b7de33c687404ec3045d4a0d437580455257c0436f858d702f244e7d652f9f07" 714 | dependencies = [ 715 | "atty", 716 | "log", 717 | "winapi 0.3.9", 718 | ] 719 | 720 | [[package]] 721 | name = "slab" 722 | version = "0.4.4" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" 725 | 726 | [[package]] 727 | name = "socket2" 728 | version = "0.3.19" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" 731 | dependencies = [ 732 | "cfg-if 1.0.0", 733 | "libc", 734 | "winapi 0.3.9", 735 | ] 736 | 737 | [[package]] 738 | name = "strsim" 739 | version = "0.8.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 742 | 743 | [[package]] 744 | name = "structopt" 745 | version = "0.3.23" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" 748 | dependencies = [ 749 | "clap", 750 | "lazy_static", 751 | "structopt-derive", 752 | ] 753 | 754 | [[package]] 755 | name = "structopt-derive" 756 | version = "0.4.16" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" 759 | dependencies = [ 760 | "heck", 761 | "proc-macro-error", 762 | "proc-macro2", 763 | "quote", 764 | "syn", 765 | ] 766 | 767 | [[package]] 768 | name = "syn" 769 | version = "1.0.76" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" 772 | dependencies = [ 773 | "proc-macro2", 774 | "quote", 775 | "unicode-xid", 776 | ] 777 | 778 | [[package]] 779 | name = "tempfile" 780 | version = "3.2.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 783 | dependencies = [ 784 | "cfg-if 1.0.0", 785 | "libc", 786 | "rand", 787 | "redox_syscall", 788 | "remove_dir_all", 789 | "winapi 0.3.9", 790 | ] 791 | 792 | [[package]] 793 | name = "textwrap" 794 | version = "0.11.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 797 | dependencies = [ 798 | "unicode-width", 799 | ] 800 | 801 | [[package]] 802 | name = "tokio" 803 | version = "0.2.25" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" 806 | dependencies = [ 807 | "bytes 0.5.6", 808 | "fnv", 809 | "futures-core", 810 | "iovec", 811 | "lazy_static", 812 | "libc", 813 | "memchr", 814 | "mio", 815 | "mio-uds", 816 | "pin-project-lite 0.1.12", 817 | "signal-hook-registry", 818 | "slab", 819 | "tokio-macros", 820 | "winapi 0.3.9", 821 | ] 822 | 823 | [[package]] 824 | name = "tokio-macros" 825 | version = "0.2.6" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" 828 | dependencies = [ 829 | "proc-macro2", 830 | "quote", 831 | "syn", 832 | ] 833 | 834 | [[package]] 835 | name = "tokio-tls" 836 | version = "0.3.1" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" 839 | dependencies = [ 840 | "native-tls", 841 | "tokio", 842 | ] 843 | 844 | [[package]] 845 | name = "tokio-util" 846 | version = "0.3.1" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 849 | dependencies = [ 850 | "bytes 0.5.6", 851 | "futures-core", 852 | "futures-sink", 853 | "log", 854 | "pin-project-lite 0.1.12", 855 | "tokio", 856 | ] 857 | 858 | [[package]] 859 | name = "tower-service" 860 | version = "0.3.1" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 863 | 864 | [[package]] 865 | name = "tracing" 866 | version = "0.1.28" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" 869 | dependencies = [ 870 | "cfg-if 1.0.0", 871 | "log", 872 | "pin-project-lite 0.2.7", 873 | "tracing-core", 874 | ] 875 | 876 | [[package]] 877 | name = "tracing-core" 878 | version = "0.1.20" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" 881 | dependencies = [ 882 | "lazy_static", 883 | ] 884 | 885 | [[package]] 886 | name = "tracing-futures" 887 | version = "0.2.5" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 890 | dependencies = [ 891 | "pin-project", 892 | "tracing", 893 | ] 894 | 895 | [[package]] 896 | name = "try-lock" 897 | version = "0.2.3" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 900 | 901 | [[package]] 902 | name = "unbound-telemetry" 903 | version = "0.1.0" 904 | dependencies = [ 905 | "approx", 906 | "async-trait", 907 | "claim", 908 | "domain", 909 | "dtoa", 910 | "hyper", 911 | "itoa", 912 | "libc", 913 | "log", 914 | "native-tls", 915 | "openssl", 916 | "simple_logger", 917 | "structopt", 918 | "tokio", 919 | "tokio-tls", 920 | ] 921 | 922 | [[package]] 923 | name = "unicode-segmentation" 924 | version = "1.8.0" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 927 | 928 | [[package]] 929 | name = "unicode-width" 930 | version = "0.1.9" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 933 | 934 | [[package]] 935 | name = "unicode-xid" 936 | version = "0.2.2" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 939 | 940 | [[package]] 941 | name = "vcpkg" 942 | version = "0.2.15" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 945 | 946 | [[package]] 947 | name = "vec_map" 948 | version = "0.8.2" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 951 | 952 | [[package]] 953 | name = "version_check" 954 | version = "0.9.3" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 957 | 958 | [[package]] 959 | name = "want" 960 | version = "0.3.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 963 | dependencies = [ 964 | "log", 965 | "try-lock", 966 | ] 967 | 968 | [[package]] 969 | name = "wasi" 970 | version = "0.10.2+wasi-snapshot-preview1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 973 | 974 | [[package]] 975 | name = "winapi" 976 | version = "0.2.8" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 979 | 980 | [[package]] 981 | name = "winapi" 982 | version = "0.3.9" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 985 | dependencies = [ 986 | "winapi-i686-pc-windows-gnu", 987 | "winapi-x86_64-pc-windows-gnu", 988 | ] 989 | 990 | [[package]] 991 | name = "winapi-build" 992 | version = "0.1.1" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 995 | 996 | [[package]] 997 | name = "winapi-i686-pc-windows-gnu" 998 | version = "0.4.0" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1001 | 1002 | [[package]] 1003 | name = "winapi-x86_64-pc-windows-gnu" 1004 | version = "0.4.0" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1007 | 1008 | [[package]] 1009 | name = "ws2_32-sys" 1010 | version = "0.2.1" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1013 | dependencies = [ 1014 | "winapi 0.2.8", 1015 | "winapi-build", 1016 | ] 1017 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unbound-telemetry" 3 | version = "0.1.0" 4 | authors = ["svartalf "] 5 | description = "Unbound DNS server telemetry exporter" 6 | keywords = ["unbound", "prometheus", "telemetry", "metrics", "exporter"] 7 | repository = "https://github.com/svartalf/unbound-telemetry" 8 | readme = "README.md" 9 | license = "MIT" 10 | edition = "2018" 11 | publish = false 12 | 13 | [dependencies] 14 | log = "^0.4" 15 | simple_logger = { version = "^1.4", default-features = false } 16 | structopt = "^0.3" 17 | tokio = { version = "^0.2", features = ["macros", "signal", "uds", "dns"] } 18 | tokio-tls = "^0.3" 19 | hyper = { version = "^0.13", default-features = false, features = ["runtime"] } 20 | async-trait = "^0.1" 21 | itoa = "^0.4" 22 | dtoa = "^0.4" 23 | native-tls = "=0.2.3" 24 | domain = "0.6.1" 25 | 26 | [target.'cfg(unix)'.dependencies] 27 | libc = "^0.2" # Used for shm access 28 | openssl = "^0.10" 29 | 30 | [dev-dependencies] 31 | approx = "^0.5" 32 | claim = "^0.5" 33 | 34 | [features] 35 | # Used for Docker builds only 36 | vendored = ["openssl/vendored"] 37 | 38 | [[bin]] 39 | name = "unbound-telemetry" 40 | path = "src/bin/server/main.rs" 41 | 42 | [profile.release] 43 | lto = true 44 | codegen-units = 1 45 | 46 | [profile.bench] 47 | debug = true 48 | 49 | [patch.crates-io] 50 | # Note: fuzzing crate has the same override 51 | native-tls = { git = "https://github.com/Goirad/rust-native-tls.git", branch = "pkcs8-squashed" } 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build stage 2 | 3 | FROM rust:latest AS build 4 | 5 | WORKDIR /root 6 | ADD . . 7 | 8 | # We can't use the `rust:alpine` image directly, 9 | # because we have proc macros crates in the dependency tree 10 | # and they can't be compiled directly on musl systems. 11 | # Cross compiling works, though, so here we are. 12 | RUN apt-get update && \ 13 | apt-get install -y musl-tools && \ 14 | rustup target add x86_64-unknown-linux-musl && \ 15 | cargo build --release --target=x86_64-unknown-linux-musl --features vendored && \ 16 | strip ./target/x86_64-unknown-linux-musl/release/unbound-telemetry 17 | 18 | # Execution stage 19 | 20 | FROM alpine:latest 21 | 22 | RUN apk --no-cache add --update curl 23 | 24 | COPY --from=build /root/target/x86_64-unknown-linux-musl/release/unbound-telemetry /bin 25 | 26 | EXPOSE 80 27 | 28 | HEALTHCHECK --timeout=1s CMD /usr/bin/curl --silent --fail http://127.0.0.1:80/healthcheck || exit 1 29 | 30 | ENTRYPOINT ["/bin/unbound-telemetry"] 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 svartalf 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unbound-telemetry 2 | 3 | ![Logo](.github/logo.png) 4 | 5 | [![Coverage Status](https://github.com/svartalf/unbound-telemetry/workflows/Continuous%20integration/badge.svg)](https://github.com/svartalf/prometheus-unbound-exporter/actions?workflow=Continuous+integration) 6 | ![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg) 7 | ![Minimum rustc version](https://img.shields.io/badge/rustc-1.40+-green.svg) 8 | 9 | > [Unbound DNS resolver](https://www.nlnetlabs.nl/projects/unbound/about/) metrics exporter for [Prometheus](https://prometheus.io) 10 | 11 | ## Deprecation notice 12 | 13 | This repo is archived and will not be maintained anymore. You can use the [`unbound_exporter`](https://github.com/letsencrypt/unbound_exporter) by Let's Encrypt instead. 14 | 15 | ## Features 16 | 17 | * Communicates with `unbound` via TLS, UDS socket or shared memory 18 | * Compatible with [kumina/unbound_exporter](https://github.com/kumina/unbound_exporter); your dashboard should just work 19 | * Small binary size (~2 Mb after `strip`) and memory footprint (~10 Kb) 20 | * Takes ~10 ms to respond with gathered metrics 21 | * Blazing fast! 22 | 23 | ## Platform support 24 | 25 | This project is developed, manually and automatically tested with Linux. 26 | 27 | Following platforms are tested in the CI environment and expected to work: 28 | 29 | * Windows 30 | * macOS 31 | 32 | It is expected that FreeBSD, NetBSD and OpenBSD will work too, but 33 | there are no any manual or automatic checks for them exist. 34 | 35 | Note that communication via UDS socket or shared memory is not supported for Windows. 36 | 37 | ## Installation 38 | 39 | ### From sources 40 | 41 | 1. [Rust](https://www.rust-lang.org/) language compiler version >= 1.46 is required 42 | 2. Clone the repository 43 | 3. Run the following command 44 | ```bash 45 | $ cargo build --release 46 | ``` 47 | 4. Get the compiled executable from the `./target/release/unbound-telemetry` 48 | 49 | ## Usage 50 | 51 | HTTP interface is available at http://0.0.0.0:9167 by default and can be changed via CLI arguments. 52 | 53 | ### TCP socket 54 | 55 | First of all, enable `remote-control` option in the [`unbound.conf`](https://nlnetlabs.nl/documentation/unbound/unbound.conf/), 56 | configure control interface address and TLS if needed. 57 | 58 | Run the following command to see possible flags and options: 59 | 60 | ```bash 61 | $ unbound-telemetry tcp --help 62 | ``` 63 | 64 | ### Unix domain socket 65 | 66 | Similar to [TLS socket](#tls-socket), you need to enable `remote-control` option first. 67 | 68 | Run the following command to see possible flags and options: 69 | 70 | ```bash 71 | $ unbound-telemetry uds --help 72 | ``` 73 | 74 | ### Shared memory 75 | 76 | Enable `shm-enable` option in the [`unbound.conf`](https://nlnetlabs.nl/documentation/unbound/unbound.conf/) 77 | and run the following command: 78 | 79 | ```bash 80 | $ unbound-telemetry shm --help 81 | ``` 82 | 83 | ### Monitoring 84 | 85 | `/healthcheck` URL can be used for automated monitoring; 86 | in case when exporter is not able to access the `unbound` instance, 87 | `HTTP 500` error will be returned, response body will contain plain text error description. 88 | 89 | ## Grafana 90 | 91 | [This Grafana dashboard](https://grafana.com/grafana/dashboards/11705) can be used 92 | to show all metrics provided by this exporter. 93 | 94 | ## License 95 | 96 | `unbound-telemetry` is released under the MIT License. 97 | 98 | ## Donations 99 | 100 | If you appreciate my work and want to support me or this project, you can do it [here](https://svartalf.info/donate). 101 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | Unbound Exporter 3 | 4 |

Unbound Exporter

5 |

Metrics

6 | 7 | -------------------------------------------------------------------------------- /assets/test_text_stats.txt: -------------------------------------------------------------------------------- 1 | thread0.num.queries=696 2 | thread0.num.queries_ip_ratelimited=0 3 | thread0.num.cachehits=119 4 | thread0.num.cachemiss=577 5 | thread0.num.prefetch=3 6 | thread0.num.zero_ttl=0 7 | thread0.num.recursivereplies=577 8 | thread0.num.dnscrypt.crypted=0 9 | thread0.num.dnscrypt.cert=0 10 | thread0.num.dnscrypt.cleartext=0 11 | thread0.num.dnscrypt.malformed=0 12 | thread0.requestlist.avg=0.756897 13 | thread0.requestlist.max=7 14 | thread0.requestlist.overwritten=0 15 | thread0.requestlist.exceeded=0 16 | thread0.requestlist.current.all=0 17 | thread0.requestlist.current.user=0 18 | thread0.recursion.time.avg=0.092912 19 | thread0.recursion.time.median=0.057344 20 | thread0.tcpusage=0 21 | thread1.num.queries=642 22 | thread1.num.queries_ip_ratelimited=0 23 | thread1.num.cachehits=101 24 | thread1.num.cachemiss=541 25 | thread1.num.prefetch=2 26 | thread1.num.zero_ttl=0 27 | thread1.num.recursivereplies=541 28 | thread1.num.dnscrypt.crypted=0 29 | thread1.num.dnscrypt.cert=0 30 | thread1.num.dnscrypt.cleartext=0 31 | thread1.num.dnscrypt.malformed=0 32 | thread1.requestlist.avg=1.03499 33 | thread1.requestlist.max=9 34 | thread1.requestlist.overwritten=0 35 | thread1.requestlist.exceeded=0 36 | thread1.requestlist.current.all=0 37 | thread1.requestlist.current.user=0 38 | thread1.recursion.time.avg=0.092580 39 | thread1.recursion.time.median=0.0538504 40 | thread1.tcpusage=0 41 | total.num.queries=1338 42 | total.num.queries_ip_ratelimited=0 43 | total.num.cachehits=220 44 | total.num.cachemiss=1118 45 | total.num.prefetch=5 46 | total.num.zero_ttl=0 47 | total.num.recursivereplies=1118 48 | total.num.dnscrypt.crypted=0 49 | total.num.dnscrypt.cert=0 50 | total.num.dnscrypt.cleartext=0 51 | total.num.dnscrypt.malformed=0 52 | total.requestlist.avg=0.891362 53 | total.requestlist.max=9 54 | total.requestlist.overwritten=0 55 | total.requestlist.exceeded=0 56 | total.requestlist.current.all=0 57 | total.requestlist.current.user=0 58 | total.recursion.time.avg=0.092751 59 | total.recursion.time.median=0.0555972 60 | total.tcpusage=0 61 | time.now=1580162982.980833 62 | time.up=32586.319193 63 | time.elapsed=32586.319193 64 | mem.cache.rrset=443440 65 | mem.cache.message=312898 66 | mem.mod.iterator=16588 67 | mem.mod.validator=140392 68 | mem.mod.respip=0 69 | mem.mod.subnet=0 70 | mem.cache.dnscrypt_shared_secret=0 71 | mem.cache.dnscrypt_nonce=0 72 | mem.streamwait=0 73 | mem.http.query_buffer=1024 74 | mem.http.response_buffer=2048 75 | histogram.000000.000000.to.000000.000001=22 76 | histogram.000000.000001.to.000000.000002=0 77 | histogram.000000.000002.to.000000.000004=0 78 | histogram.000000.000004.to.000000.000008=0 79 | histogram.000000.000008.to.000000.000016=0 80 | histogram.000000.000016.to.000000.000032=0 81 | histogram.000000.000032.to.000000.000064=0 82 | histogram.000000.000064.to.000000.000128=0 83 | histogram.000000.000128.to.000000.000256=0 84 | histogram.000000.000256.to.000000.000512=0 85 | histogram.000000.000512.to.000000.001024=0 86 | histogram.000000.001024.to.000000.002048=0 87 | histogram.000000.002048.to.000000.004096=32 88 | histogram.000000.004096.to.000000.008192=98 89 | histogram.000000.008192.to.000000.016384=55 90 | histogram.000000.016384.to.000000.032768=152 91 | histogram.000000.032768.to.000000.065536=286 92 | histogram.000000.065536.to.000000.131072=207 93 | histogram.000000.131072.to.000000.262144=205 94 | histogram.000000.262144.to.000000.524288=55 95 | histogram.000000.524288.to.000001.000000=3 96 | histogram.000001.000000.to.000002.000000=1 97 | histogram.000002.000000.to.000004.000000=2 98 | histogram.000004.000000.to.000008.000000=0 99 | histogram.000008.000000.to.000016.000000=0 100 | histogram.000016.000000.to.000032.000000=0 101 | histogram.000032.000000.to.000064.000000=0 102 | histogram.000064.000000.to.000128.000000=0 103 | histogram.000128.000000.to.000256.000000=0 104 | histogram.000256.000000.to.000512.000000=0 105 | histogram.000512.000000.to.001024.000000=0 106 | histogram.001024.000000.to.002048.000000=0 107 | histogram.002048.000000.to.004096.000000=0 108 | histogram.004096.000000.to.008192.000000=0 109 | histogram.008192.000000.to.016384.000000=0 110 | histogram.016384.000000.to.032768.000000=0 111 | histogram.032768.000000.to.065536.000000=0 112 | histogram.065536.000000.to.131072.000000=0 113 | histogram.131072.000000.to.262144.000000=0 114 | histogram.262144.000000.to.524288.000000=0 115 | num.query.type.TYPE0=21838 116 | num.query.type.A=4576639648 117 | num.query.type.NS=195714 118 | num.query.type.MF=1 119 | num.query.type.CNAME=223477 120 | num.query.type.SOA=252841 121 | num.query.type.MR=2 122 | num.query.type.NULL=238628 123 | num.query.type.WKS=230227 124 | num.query.type.PTR=8446520 125 | num.query.type.HINFO=231428 126 | num.query.type.MX=34204 127 | num.query.type.TXT=510353 128 | num.query.type.AAAA=151992709 129 | num.query.type.NSAP-PTR=1 130 | num.query.type.NXT=3 131 | num.query.type.SRV=1883446 132 | num.query.type.NAPTR=1578821 133 | num.query.type.DS=215 134 | num.query.type.DNSKEY=252 135 | num.query.type.TYPE65=140375490 136 | num.query.type.TYPE96=1 137 | num.query.type.ANY=82558 138 | num.query.type.other=17173 139 | num.query.class.CLASS0=2 140 | num.query.class.IN=4882932525 141 | num.query.class.CH=1073 142 | num.query.class.HS=3 143 | num.query.class.CLASS5=1 144 | num.query.class.ANY=296 145 | num.query.class.other=21945 146 | num.query.opcode.QUERY=1338 147 | num.query.tcp=0 148 | num.query.tcpout=2 149 | num.query.tls=0 150 | num.query.tls.resume=0 151 | num.query.ipv6=0 152 | num.query.https=10 153 | num.query.flags.QR=0 154 | num.query.flags.AA=0 155 | num.query.flags.TC=0 156 | num.query.flags.RD=1338 157 | num.query.flags.RA=0 158 | num.query.flags.Z=0 159 | num.query.flags.AD=0 160 | num.query.flags.CD=0 161 | num.query.edns.present=0 162 | num.query.edns.DO=0 163 | num.answer.rcode.NOERROR=1315 164 | num.answer.rcode.FORMERR=0 165 | num.answer.rcode.SERVFAIL=0 166 | num.answer.rcode.NXDOMAIN=23 167 | num.answer.rcode.NOTIMPL=0 168 | num.answer.rcode.REFUSED=0 169 | num.answer.rcode.nodata=190 170 | num.query.ratelimited=0 171 | num.answer.secure=70 172 | num.answer.bogus=0 173 | num.rrset.bogus=1 174 | num.query.aggressive.NOERROR=0 175 | num.query.aggressive.NXDOMAIN=0 176 | unwanted.queries=0 177 | unwanted.replies=0 178 | msg.cache.count=885 179 | rrset.cache.count=1019 180 | infra.cache.count=2 181 | key.cache.count=196 182 | dnscrypt_shared_secret.cache.count=0 183 | dnscrypt_nonce.cache.count=0 184 | num.query.dnscrypt.shared_secret.cachemiss=0 185 | num.query.dnscrypt.replay=0 186 | num.query.authzone.up=0 187 | num.query.authzone.down=0 188 | num.query.subnet=0 189 | num.query.subnet_cache=0 190 | -------------------------------------------------------------------------------- /assets/test_text_stats_1_13_2.txt: -------------------------------------------------------------------------------- 1 | thread0.num.queries=0 2 | thread0.num.queries_ip_ratelimited=0 3 | thread0.num.cachehits=0 4 | thread0.num.cachemiss=0 5 | thread0.num.prefetch=0 6 | thread0.num.expired=0 7 | thread0.num.recursivereplies=0 8 | thread0.num.dnscrypt.crypted=0 9 | thread0.num.dnscrypt.cert=0 10 | thread0.num.dnscrypt.cleartext=0 11 | thread0.num.dnscrypt.malformed=0 12 | thread0.requestlist.avg=0 13 | thread0.requestlist.max=0 14 | thread0.requestlist.overwritten=0 15 | thread0.requestlist.exceeded=0 16 | thread0.requestlist.current.all=0 17 | thread0.requestlist.current.user=0 18 | thread0.recursion.time.avg=0.000000 19 | thread0.recursion.time.median=0 20 | thread0.tcpusage=0 21 | total.num.queries=0 22 | total.num.queries_ip_ratelimited=0 23 | total.num.cachehits=0 24 | total.num.cachemiss=0 25 | total.num.prefetch=0 26 | total.num.expired=0 27 | total.num.recursivereplies=0 28 | total.num.dnscrypt.crypted=0 29 | total.num.dnscrypt.cert=0 30 | total.num.dnscrypt.cleartext=0 31 | total.num.dnscrypt.malformed=0 32 | total.requestlist.avg=0 33 | total.requestlist.max=0 34 | total.requestlist.overwritten=0 35 | total.requestlist.exceeded=0 36 | total.requestlist.current.all=0 37 | total.requestlist.current.user=0 38 | total.recursion.time.avg=0.000000 39 | total.recursion.time.median=0 40 | total.tcpusage=0 41 | time.now=1629245344.643864 42 | time.up=20682.110930 43 | time.elapsed=17.851862 44 | mem.cache.rrset=538823 45 | mem.cache.message=673235 46 | mem.mod.iterator=16556 47 | mem.mod.validator=66288 48 | mem.mod.respip=0 49 | mem.cache.dnscrypt_shared_secret=0 50 | mem.cache.dnscrypt_nonce=0 51 | mem.streamwait=0 52 | mem.http.query_buffer=0 53 | mem.http.response_buffer=0 54 | histogram.000000.000000.to.000000.000001=0 55 | histogram.000000.000001.to.000000.000002=0 56 | histogram.000000.000002.to.000000.000004=0 57 | histogram.000000.000004.to.000000.000008=0 58 | histogram.000000.000008.to.000000.000016=0 59 | histogram.000000.000016.to.000000.000032=0 60 | histogram.000000.000032.to.000000.000064=0 61 | histogram.000000.000064.to.000000.000128=0 62 | histogram.000000.000128.to.000000.000256=0 63 | histogram.000000.000256.to.000000.000512=0 64 | histogram.000000.000512.to.000000.001024=0 65 | histogram.000000.001024.to.000000.002048=0 66 | histogram.000000.002048.to.000000.004096=0 67 | histogram.000000.004096.to.000000.008192=0 68 | histogram.000000.008192.to.000000.016384=0 69 | histogram.000000.016384.to.000000.032768=0 70 | histogram.000000.032768.to.000000.065536=0 71 | histogram.000000.065536.to.000000.131072=0 72 | histogram.000000.131072.to.000000.262144=0 73 | histogram.000000.262144.to.000000.524288=0 74 | histogram.000000.524288.to.000001.000000=0 75 | histogram.000001.000000.to.000002.000000=0 76 | histogram.000002.000000.to.000004.000000=0 77 | histogram.000004.000000.to.000008.000000=0 78 | histogram.000008.000000.to.000016.000000=0 79 | histogram.000016.000000.to.000032.000000=0 80 | histogram.000032.000000.to.000064.000000=0 81 | histogram.000064.000000.to.000128.000000=0 82 | histogram.000128.000000.to.000256.000000=0 83 | histogram.000256.000000.to.000512.000000=0 84 | histogram.000512.000000.to.001024.000000=0 85 | histogram.001024.000000.to.002048.000000=0 86 | histogram.002048.000000.to.004096.000000=0 87 | histogram.004096.000000.to.008192.000000=0 88 | histogram.008192.000000.to.016384.000000=0 89 | histogram.016384.000000.to.032768.000000=0 90 | histogram.032768.000000.to.065536.000000=0 91 | histogram.065536.000000.to.131072.000000=0 92 | histogram.131072.000000.to.262144.000000=0 93 | histogram.262144.000000.to.524288.000000=0 94 | num.query.tcp=0 95 | num.query.tcpout=0 96 | num.query.tls=0 97 | num.query.tls.resume=0 98 | num.query.ipv6=0 99 | num.query.https=0 100 | num.query.flags.QR=0 101 | num.query.flags.AA=0 102 | num.query.flags.TC=0 103 | num.query.flags.RD=0 104 | num.query.flags.RA=0 105 | num.query.flags.Z=0 106 | num.query.flags.AD=0 107 | num.query.flags.CD=0 108 | num.query.edns.present=0 109 | num.query.edns.DO=0 110 | num.answer.rcode.NOERROR=0 111 | num.answer.rcode.FORMERR=0 112 | num.answer.rcode.SERVFAIL=0 113 | num.answer.rcode.NXDOMAIN=0 114 | num.answer.rcode.NOTIMPL=0 115 | num.answer.rcode.REFUSED=0 116 | num.query.ratelimited=0 117 | num.answer.secure=0 118 | num.answer.bogus=0 119 | num.rrset.bogus=0 120 | num.query.aggressive.NOERROR=0 121 | num.query.aggressive.NXDOMAIN=0 122 | unwanted.queries=0 123 | unwanted.replies=0 124 | msg.cache.count=2580 125 | rrset.cache.count=1844 126 | infra.cache.count=4 127 | key.cache.count=0 128 | dnscrypt_shared_secret.cache.count=0 129 | dnscrypt_nonce.cache.count=0 130 | num.query.dnscrypt.shared_secret.cachemiss=0 131 | num.query.dnscrypt.replay=0 132 | num.query.authzone.up=0 133 | num.query.authzone.down=0 134 | num.query.type.HTTPS=10 135 | num.query.type.SVCB=45 136 | -------------------------------------------------------------------------------- /benches/text_parser.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use std::str::FromStr; 6 | use test::Bencher; 7 | 8 | use unbound_telemetry::Statistics; 9 | 10 | static STATS: &str = include_str!("../assets/test_text_stats.txt"); 11 | 12 | #[bench] 13 | fn bench_parser(b: &mut Bencher) { 14 | b.iter(|| Statistics::from_str(STATS)); 15 | } 16 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi 0.3.8", 10 | ] 11 | 12 | [[package]] 13 | name = "arbitrary" 14 | version = "0.3.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "491d5e42b1a073ff1fc1e0a02744b3f8bee9cf4bfd552053cac36c64b879795d" 17 | dependencies = [ 18 | "derive_arbitrary", 19 | ] 20 | 21 | [[package]] 22 | name = "arc-swap" 23 | version = "0.4.4" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" 26 | 27 | [[package]] 28 | name = "async-trait" 29 | version = "0.1.24" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1" 32 | dependencies = [ 33 | "proc-macro2", 34 | "quote", 35 | "syn", 36 | ] 37 | 38 | [[package]] 39 | name = "atty" 40 | version = "0.2.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 43 | dependencies = [ 44 | "hermit-abi", 45 | "libc", 46 | "winapi 0.3.8", 47 | ] 48 | 49 | [[package]] 50 | name = "autocfg" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 54 | 55 | [[package]] 56 | name = "bitflags" 57 | version = "1.2.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 60 | 61 | [[package]] 62 | name = "bytes" 63 | version = "0.5.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 66 | 67 | [[package]] 68 | name = "c2-chacha" 69 | version = "0.2.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" 72 | dependencies = [ 73 | "ppv-lite86", 74 | ] 75 | 76 | [[package]] 77 | name = "cc" 78 | version = "1.0.50" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 81 | 82 | [[package]] 83 | name = "cfg-if" 84 | version = "0.1.10" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 87 | 88 | [[package]] 89 | name = "clap" 90 | version = "2.33.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 93 | dependencies = [ 94 | "ansi_term", 95 | "atty", 96 | "bitflags", 97 | "strsim", 98 | "textwrap", 99 | "unicode-width", 100 | "vec_map", 101 | ] 102 | 103 | [[package]] 104 | name = "core-foundation" 105 | version = "0.6.4" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 108 | dependencies = [ 109 | "core-foundation-sys", 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "core-foundation-sys" 115 | version = "0.6.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 118 | 119 | [[package]] 120 | name = "derive_arbitrary" 121 | version = "0.3.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "dab2f0544254a47cabc58956cc7ebda74c3b796bb2761e3fe8f29fdde632ad95" 124 | dependencies = [ 125 | "proc-macro2", 126 | "syn", 127 | "synstructure", 128 | ] 129 | 130 | [[package]] 131 | name = "dtoa" 132 | version = "0.4.5" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" 135 | 136 | [[package]] 137 | name = "fnv" 138 | version = "1.0.6" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 141 | 142 | [[package]] 143 | name = "foreign-types" 144 | version = "0.3.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 147 | dependencies = [ 148 | "foreign-types-shared", 149 | ] 150 | 151 | [[package]] 152 | name = "foreign-types-shared" 153 | version = "0.1.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 156 | 157 | [[package]] 158 | name = "fuchsia-zircon" 159 | version = "0.3.3" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 162 | dependencies = [ 163 | "bitflags", 164 | "fuchsia-zircon-sys", 165 | ] 166 | 167 | [[package]] 168 | name = "fuchsia-zircon-sys" 169 | version = "0.3.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 172 | 173 | [[package]] 174 | name = "futures-channel" 175 | version = "0.3.3" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "7264eb65b194d2fa6ec31b898ead7c332854bfa42521659226e72a585fca5b85" 178 | dependencies = [ 179 | "futures-core", 180 | ] 181 | 182 | [[package]] 183 | name = "futures-core" 184 | version = "0.3.3" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "b597b16aa1a19ce2dfde5128a7c656d75346b35601a640be2d9efd4e9c83609d" 187 | 188 | [[package]] 189 | name = "futures-sink" 190 | version = "0.3.3" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "04299e123547ea7c56f3e1b376703142f5fc0b6700433eed549e9d0b8a75a66c" 193 | 194 | [[package]] 195 | name = "futures-task" 196 | version = "0.3.3" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "86f9ceab4bce46555ee608b1ec7c414d6b2e76e196ef46fa5a8d4815a8571398" 199 | 200 | [[package]] 201 | name = "futures-util" 202 | version = "0.3.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "7d2f1296f7644d2cd908ebb2fa74645608e39f117c72bac251d40418c6d74c4f" 205 | dependencies = [ 206 | "futures-core", 207 | "futures-task", 208 | "pin-utils", 209 | ] 210 | 211 | [[package]] 212 | name = "getrandom" 213 | version = "0.1.14" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 216 | dependencies = [ 217 | "cfg-if", 218 | "libc", 219 | "wasi", 220 | ] 221 | 222 | [[package]] 223 | name = "h2" 224 | version = "0.2.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" 227 | dependencies = [ 228 | "bytes", 229 | "fnv", 230 | "futures-core", 231 | "futures-sink", 232 | "futures-util", 233 | "http", 234 | "indexmap", 235 | "log", 236 | "slab", 237 | "tokio", 238 | "tokio-util", 239 | ] 240 | 241 | [[package]] 242 | name = "heck" 243 | version = "0.3.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 246 | dependencies = [ 247 | "unicode-segmentation", 248 | ] 249 | 250 | [[package]] 251 | name = "hermit-abi" 252 | version = "0.1.6" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" 255 | dependencies = [ 256 | "libc", 257 | ] 258 | 259 | [[package]] 260 | name = "http" 261 | version = "0.2.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" 264 | dependencies = [ 265 | "bytes", 266 | "fnv", 267 | "itoa", 268 | ] 269 | 270 | [[package]] 271 | name = "http-body" 272 | version = "0.3.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 275 | dependencies = [ 276 | "bytes", 277 | "http", 278 | ] 279 | 280 | [[package]] 281 | name = "httparse" 282 | version = "1.3.4" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 285 | 286 | [[package]] 287 | name = "hyper" 288 | version = "0.13.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "fa1c527bbc634be72aa7ba31e4e4def9bbb020f5416916279b7c705cd838893e" 291 | dependencies = [ 292 | "bytes", 293 | "futures-channel", 294 | "futures-core", 295 | "futures-util", 296 | "h2", 297 | "http", 298 | "http-body", 299 | "httparse", 300 | "itoa", 301 | "log", 302 | "net2", 303 | "pin-project", 304 | "time", 305 | "tokio", 306 | "tower-service", 307 | "want", 308 | ] 309 | 310 | [[package]] 311 | name = "indexmap" 312 | version = "1.3.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" 315 | dependencies = [ 316 | "autocfg", 317 | ] 318 | 319 | [[package]] 320 | name = "iovec" 321 | version = "0.1.4" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 324 | dependencies = [ 325 | "libc", 326 | ] 327 | 328 | [[package]] 329 | name = "itoa" 330 | version = "0.4.5" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 333 | 334 | [[package]] 335 | name = "kernel32-sys" 336 | version = "0.2.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 339 | dependencies = [ 340 | "winapi 0.2.8", 341 | "winapi-build", 342 | ] 343 | 344 | [[package]] 345 | name = "lazy_static" 346 | version = "1.4.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 349 | 350 | [[package]] 351 | name = "libc" 352 | version = "0.2.66" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 355 | 356 | [[package]] 357 | name = "libfuzzer-sys" 358 | version = "0.2.1" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "3e969cd2be7a2aae0acbbe205da49134148db2287fb45a811bf441ed72f09a35" 361 | dependencies = [ 362 | "arbitrary", 363 | "cc", 364 | ] 365 | 366 | [[package]] 367 | name = "log" 368 | version = "0.4.8" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 371 | dependencies = [ 372 | "cfg-if", 373 | ] 374 | 375 | [[package]] 376 | name = "memchr" 377 | version = "2.3.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" 380 | 381 | [[package]] 382 | name = "mio" 383 | version = "0.6.21" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" 386 | dependencies = [ 387 | "cfg-if", 388 | "fuchsia-zircon", 389 | "fuchsia-zircon-sys", 390 | "iovec", 391 | "kernel32-sys", 392 | "libc", 393 | "log", 394 | "miow", 395 | "net2", 396 | "slab", 397 | "winapi 0.2.8", 398 | ] 399 | 400 | [[package]] 401 | name = "mio-uds" 402 | version = "0.6.7" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" 405 | dependencies = [ 406 | "iovec", 407 | "libc", 408 | "mio", 409 | ] 410 | 411 | [[package]] 412 | name = "miow" 413 | version = "0.2.1" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 416 | dependencies = [ 417 | "kernel32-sys", 418 | "net2", 419 | "winapi 0.2.8", 420 | "ws2_32-sys", 421 | ] 422 | 423 | [[package]] 424 | name = "native-tls" 425 | version = "0.2.3" 426 | source = "git+https://github.com/Goirad/rust-native-tls.git?branch=pkcs8-squashed#28eade4430e08b147fc35d0e6996413671f9a9f1" 427 | dependencies = [ 428 | "lazy_static", 429 | "libc", 430 | "log", 431 | "openssl", 432 | "openssl-probe", 433 | "openssl-sys", 434 | "rustc-serialize", 435 | "schannel", 436 | "security-framework", 437 | "security-framework-sys", 438 | "tempfile", 439 | ] 440 | 441 | [[package]] 442 | name = "net2" 443 | version = "0.2.33" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 446 | dependencies = [ 447 | "cfg-if", 448 | "libc", 449 | "winapi 0.3.8", 450 | ] 451 | 452 | [[package]] 453 | name = "openssl" 454 | version = "0.10.28" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" 457 | dependencies = [ 458 | "bitflags", 459 | "cfg-if", 460 | "foreign-types", 461 | "lazy_static", 462 | "libc", 463 | "openssl-sys", 464 | ] 465 | 466 | [[package]] 467 | name = "openssl-probe" 468 | version = "0.1.2" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 471 | 472 | [[package]] 473 | name = "openssl-sys" 474 | version = "0.9.54" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" 477 | dependencies = [ 478 | "autocfg", 479 | "cc", 480 | "libc", 481 | "pkg-config", 482 | "vcpkg", 483 | ] 484 | 485 | [[package]] 486 | name = "pin-project" 487 | version = "0.4.8" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" 490 | dependencies = [ 491 | "pin-project-internal", 492 | ] 493 | 494 | [[package]] 495 | name = "pin-project-internal" 496 | version = "0.4.8" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" 499 | dependencies = [ 500 | "proc-macro2", 501 | "quote", 502 | "syn", 503 | ] 504 | 505 | [[package]] 506 | name = "pin-project-lite" 507 | version = "0.1.4" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" 510 | 511 | [[package]] 512 | name = "pin-utils" 513 | version = "0.1.0-alpha.4" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" 516 | 517 | [[package]] 518 | name = "pkg-config" 519 | version = "0.3.17" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 522 | 523 | [[package]] 524 | name = "ppv-lite86" 525 | version = "0.2.6" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 528 | 529 | [[package]] 530 | name = "proc-macro-error" 531 | version = "0.4.8" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" 534 | dependencies = [ 535 | "proc-macro-error-attr", 536 | "proc-macro2", 537 | "quote", 538 | "rustversion", 539 | "syn", 540 | ] 541 | 542 | [[package]] 543 | name = "proc-macro-error-attr" 544 | version = "0.4.8" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" 547 | dependencies = [ 548 | "proc-macro2", 549 | "quote", 550 | "rustversion", 551 | "syn", 552 | "syn-mid", 553 | ] 554 | 555 | [[package]] 556 | name = "proc-macro2" 557 | version = "1.0.8" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" 560 | dependencies = [ 561 | "unicode-xid", 562 | ] 563 | 564 | [[package]] 565 | name = "quote" 566 | version = "1.0.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 569 | dependencies = [ 570 | "proc-macro2", 571 | ] 572 | 573 | [[package]] 574 | name = "rand" 575 | version = "0.7.3" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 578 | dependencies = [ 579 | "getrandom", 580 | "libc", 581 | "rand_chacha", 582 | "rand_core", 583 | "rand_hc", 584 | ] 585 | 586 | [[package]] 587 | name = "rand_chacha" 588 | version = "0.2.1" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 591 | dependencies = [ 592 | "c2-chacha", 593 | "rand_core", 594 | ] 595 | 596 | [[package]] 597 | name = "rand_core" 598 | version = "0.5.1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 601 | dependencies = [ 602 | "getrandom", 603 | ] 604 | 605 | [[package]] 606 | name = "rand_hc" 607 | version = "0.2.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 610 | dependencies = [ 611 | "rand_core", 612 | ] 613 | 614 | [[package]] 615 | name = "redox_syscall" 616 | version = "0.1.56" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 619 | 620 | [[package]] 621 | name = "remove_dir_all" 622 | version = "0.5.2" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 625 | dependencies = [ 626 | "winapi 0.3.8", 627 | ] 628 | 629 | [[package]] 630 | name = "rustc-serialize" 631 | version = "0.3.24" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 634 | 635 | [[package]] 636 | name = "rustversion" 637 | version = "1.0.2" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" 640 | dependencies = [ 641 | "proc-macro2", 642 | "quote", 643 | "syn", 644 | ] 645 | 646 | [[package]] 647 | name = "schannel" 648 | version = "0.1.17" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" 651 | dependencies = [ 652 | "lazy_static", 653 | "winapi 0.3.8", 654 | ] 655 | 656 | [[package]] 657 | name = "security-framework" 658 | version = "0.3.4" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" 661 | dependencies = [ 662 | "core-foundation", 663 | "core-foundation-sys", 664 | "libc", 665 | "security-framework-sys", 666 | ] 667 | 668 | [[package]] 669 | name = "security-framework-sys" 670 | version = "0.3.3" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" 673 | dependencies = [ 674 | "core-foundation-sys", 675 | ] 676 | 677 | [[package]] 678 | name = "signal-hook-registry" 679 | version = "1.2.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 682 | dependencies = [ 683 | "arc-swap", 684 | "libc", 685 | ] 686 | 687 | [[package]] 688 | name = "simple_logger" 689 | version = "1.5.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "ea8a5d6557565318b2bbf1d349512b7e4b4b43fd1e759dec8acd7bc0e9b1256b" 692 | dependencies = [ 693 | "atty", 694 | "log", 695 | "winapi 0.3.8", 696 | ] 697 | 698 | [[package]] 699 | name = "slab" 700 | version = "0.4.2" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 703 | 704 | [[package]] 705 | name = "strsim" 706 | version = "0.8.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 709 | 710 | [[package]] 711 | name = "structopt" 712 | version = "0.3.9" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" 715 | dependencies = [ 716 | "clap", 717 | "lazy_static", 718 | "structopt-derive", 719 | ] 720 | 721 | [[package]] 722 | name = "structopt-derive" 723 | version = "0.4.2" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" 726 | dependencies = [ 727 | "heck", 728 | "proc-macro-error", 729 | "proc-macro2", 730 | "quote", 731 | "syn", 732 | ] 733 | 734 | [[package]] 735 | name = "syn" 736 | version = "1.0.14" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" 739 | dependencies = [ 740 | "proc-macro2", 741 | "quote", 742 | "unicode-xid", 743 | ] 744 | 745 | [[package]] 746 | name = "syn-mid" 747 | version = "0.5.0" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" 750 | dependencies = [ 751 | "proc-macro2", 752 | "quote", 753 | "syn", 754 | ] 755 | 756 | [[package]] 757 | name = "synstructure" 758 | version = "0.12.3" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 761 | dependencies = [ 762 | "proc-macro2", 763 | "quote", 764 | "syn", 765 | "unicode-xid", 766 | ] 767 | 768 | [[package]] 769 | name = "tempfile" 770 | version = "3.1.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 773 | dependencies = [ 774 | "cfg-if", 775 | "libc", 776 | "rand", 777 | "redox_syscall", 778 | "remove_dir_all", 779 | "winapi 0.3.8", 780 | ] 781 | 782 | [[package]] 783 | name = "textwrap" 784 | version = "0.11.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 787 | dependencies = [ 788 | "unicode-width", 789 | ] 790 | 791 | [[package]] 792 | name = "time" 793 | version = "0.1.42" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 796 | dependencies = [ 797 | "libc", 798 | "redox_syscall", 799 | "winapi 0.3.8", 800 | ] 801 | 802 | [[package]] 803 | name = "tokio" 804 | version = "0.2.11" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b" 807 | dependencies = [ 808 | "bytes", 809 | "fnv", 810 | "iovec", 811 | "lazy_static", 812 | "libc", 813 | "memchr", 814 | "mio", 815 | "mio-uds", 816 | "pin-project-lite", 817 | "signal-hook-registry", 818 | "slab", 819 | "tokio-macros", 820 | "winapi 0.3.8", 821 | ] 822 | 823 | [[package]] 824 | name = "tokio-macros" 825 | version = "0.2.4" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "f4b1e7ed7d5d4c2af3d999904b0eebe76544897cdbfb2b9684bed2174ab20f7c" 828 | dependencies = [ 829 | "proc-macro2", 830 | "quote", 831 | "syn", 832 | ] 833 | 834 | [[package]] 835 | name = "tokio-tls" 836 | version = "0.3.0" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" 839 | dependencies = [ 840 | "native-tls", 841 | "tokio", 842 | ] 843 | 844 | [[package]] 845 | name = "tokio-util" 846 | version = "0.2.0" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" 849 | dependencies = [ 850 | "bytes", 851 | "futures-core", 852 | "futures-sink", 853 | "log", 854 | "pin-project-lite", 855 | "tokio", 856 | ] 857 | 858 | [[package]] 859 | name = "tower-service" 860 | version = "0.3.0" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 863 | 864 | [[package]] 865 | name = "try-lock" 866 | version = "0.2.2" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 869 | 870 | [[package]] 871 | name = "unbound-telemetry" 872 | version = "0.1.0" 873 | dependencies = [ 874 | "arbitrary", 875 | "async-trait", 876 | "dtoa", 877 | "hyper", 878 | "itoa", 879 | "libc", 880 | "log", 881 | "native-tls", 882 | "openssl", 883 | "simple_logger", 884 | "structopt", 885 | "tokio", 886 | "tokio-tls", 887 | ] 888 | 889 | [[package]] 890 | name = "unbound-telemetry-fuzz" 891 | version = "0.0.0" 892 | dependencies = [ 893 | "libfuzzer-sys", 894 | "unbound-telemetry", 895 | ] 896 | 897 | [[package]] 898 | name = "unicode-segmentation" 899 | version = "1.6.0" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 902 | 903 | [[package]] 904 | name = "unicode-width" 905 | version = "0.1.7" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 908 | 909 | [[package]] 910 | name = "unicode-xid" 911 | version = "0.2.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 914 | 915 | [[package]] 916 | name = "vcpkg" 917 | version = "0.2.8" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" 920 | 921 | [[package]] 922 | name = "vec_map" 923 | version = "0.8.1" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 926 | 927 | [[package]] 928 | name = "want" 929 | version = "0.3.0" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 932 | dependencies = [ 933 | "log", 934 | "try-lock", 935 | ] 936 | 937 | [[package]] 938 | name = "wasi" 939 | version = "0.9.0+wasi-snapshot-preview1" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 942 | 943 | [[package]] 944 | name = "winapi" 945 | version = "0.2.8" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 948 | 949 | [[package]] 950 | name = "winapi" 951 | version = "0.3.8" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 954 | dependencies = [ 955 | "winapi-i686-pc-windows-gnu", 956 | "winapi-x86_64-pc-windows-gnu", 957 | ] 958 | 959 | [[package]] 960 | name = "winapi-build" 961 | version = "0.1.1" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 964 | 965 | [[package]] 966 | name = "winapi-i686-pc-windows-gnu" 967 | version = "0.4.0" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 970 | 971 | [[package]] 972 | name = "winapi-x86_64-pc-windows-gnu" 973 | version = "0.4.0" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 976 | 977 | [[package]] 978 | name = "ws2_32-sys" 979 | version = "0.2.1" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 982 | dependencies = [ 983 | "winapi 0.2.8", 984 | "winapi-build", 985 | ] 986 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "unbound-telemetry-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.2" 14 | 15 | [dependencies.unbound-telemetry] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "fuzz_measurement_observe" 24 | path = "fuzz_targets/fuzz_measurement_observe.rs" 25 | 26 | [[bin]] 27 | name = "fuzz_text_parser" 28 | path = "fuzz_targets/fuzz_text_parser.rs" 29 | 30 | [patch.crates-io] 31 | native-tls = { git = "https://github.com/Goirad/rust-native-tls.git", branch = "pkcs8-squashed" } 32 | -------------------------------------------------------------------------------- /fuzz/dicts/text_parser.txt: -------------------------------------------------------------------------------- 1 | "thread0.num.queries=" 2 | "thread0.num.queries_ip_ratelimited=" 3 | "thread0.num.cachehits=" 4 | "thread0.num.cachemiss=" 5 | "thread0.num.prefetch=" 6 | "thread0.num.zero_ttl=" 7 | "thread0.num.recursivereplies=" 8 | "thread0.num.dnscrypt.crypted=" 9 | "thread0.num.dnscrypt.cert=" 10 | "thread0.num.dnscrypt.cleartext=" 11 | "thread0.num.dnscrypt.malformed=" 12 | "thread0.requestlist.avg=" 13 | "thread0.requestlist.max=" 14 | "thread0.requestlist.overwritten=" 15 | "thread0.requestlist.exceeded=" 16 | "thread0.requestlist.current.all=" 17 | "thread0.requestlist.current.user=" 18 | "thread0.recursion.time.avg=" 19 | "thread0.recursion.time.median=" 20 | "thread0.tcpusage=" 21 | "thread1.num.queries=" 22 | "thread1.num.queries_ip_ratelimited=" 23 | "thread1.num.cachehits=" 24 | "thread1.num.cachemiss=" 25 | "thread1.num.prefetch=" 26 | "thread1.num.zero_ttl=" 27 | "thread1.num.recursivereplies=" 28 | "thread1.num.dnscrypt.crypted=" 29 | "thread1.num.dnscrypt.cert=" 30 | "thread1.num.dnscrypt.cleartext=" 31 | "thread1.num.dnscrypt.malformed=" 32 | "thread1.requestlist.avg=" 33 | "thread1.requestlist.max=" 34 | "thread1.requestlist.overwritten=" 35 | "thread1.requestlist.exceeded=" 36 | "thread1.requestlist.current.all=" 37 | "thread1.requestlist.current.user=" 38 | "thread1.recursion.time.avg=" 39 | "thread1.recursion.time.median=" 40 | "thread1.tcpusage=" 41 | "total.num.queries=" 42 | "total.num.queries_ip_ratelimited=" 43 | "total.num.cachehits=" 44 | "total.num.cachemiss=" 45 | "total.num.prefetch=" 46 | "total.num.zero_ttl=" 47 | "total.num.recursivereplies=" 48 | "total.num.dnscrypt.crypted=" 49 | "total.num.dnscrypt.cert=" 50 | "total.num.dnscrypt.cleartext=" 51 | "total.num.dnscrypt.malformed=" 52 | "total.requestlist.avg=" 53 | "total.requestlist.max=" 54 | "total.requestlist.overwritten=" 55 | "total.requestlist.exceeded=" 56 | "total.requestlist.current.all=" 57 | "total.requestlist.current.user=" 58 | "total.recursion.time.avg=" 59 | "total.recursion.time.median=" 60 | "total.tcpusage=" 61 | "time.now=" 62 | "time.up=" 63 | "time.elapsed=" 64 | "mem.cache.rrset=" 65 | "mem.cache.message=" 66 | "mem.mod.iterator=" 67 | "mem.mod.validator=" 68 | "mem.mod.respip=" 69 | "mem.mod.subnet=" 70 | "mem.cache.dnscrypt_shared_secret=" 71 | "mem.cache.dnscrypt_nonce=" 72 | "mem.streamwait=" 73 | "histogram.000000.000000.to.000000.000001=" 74 | "histogram.000000.000001.to.000000.000002=" 75 | "histogram.000000.000002.to.000000.000004=" 76 | "histogram.000000.000004.to.000000.000008=" 77 | "histogram.000000.000008.to.000000.000016=" 78 | "histogram.000000.000016.to.000000.000032=" 79 | "histogram.000000.000032.to.000000.000064=" 80 | "histogram.000000.000064.to.000000.000128=" 81 | "histogram.000000.000128.to.000000.000256=" 82 | "histogram.000000.000256.to.000000.000512=" 83 | "histogram.000000.000512.to.000000.001024=" 84 | "histogram.000000.001024.to.000000.002048=" 85 | "histogram.000000.002048.to.000000.004096=" 86 | "histogram.000000.004096.to.000000.008192=" 87 | "histogram.000000.008192.to.000000.016384=" 88 | "histogram.000000.016384.to.000000.032768=" 89 | "histogram.000000.032768.to.000000.065536=" 90 | "histogram.000000.065536.to.000000.131072=" 91 | "histogram.000000.131072.to.000000.262144=" 92 | "histogram.000000.262144.to.000000.524288=" 93 | "histogram.000000.524288.to.000001.000000=" 94 | "histogram.000001.000000.to.000002.000000=" 95 | "histogram.000002.000000.to.000004.000000=" 96 | "histogram.000004.000000.to.000008.000000=" 97 | "histogram.000008.000000.to.000016.000000=" 98 | "histogram.000016.000000.to.000032.000000=" 99 | "histogram.000032.000000.to.000064.000000=" 100 | "histogram.000064.000000.to.000128.000000=" 101 | "histogram.000128.000000.to.000256.000000=" 102 | "histogram.000256.000000.to.000512.000000=" 103 | "histogram.000512.000000.to.001024.000000=" 104 | "histogram.001024.000000.to.002048.000000=" 105 | "histogram.002048.000000.to.004096.000000=" 106 | "histogram.004096.000000.to.008192.000000=" 107 | "histogram.008192.000000.to.016384.000000=" 108 | "histogram.016384.000000.to.032768.000000=" 109 | "histogram.032768.000000.to.065536.000000=" 110 | "histogram.065536.000000.to.131072.000000=" 111 | "histogram.131072.000000.to.262144.000000=" 112 | "histogram.262144.000000.to.524288.000000=" 113 | "num.query.type.A=" 114 | "num.query.type.PTR=" 115 | "num.query.type.AAAA=" 116 | "num.query.class.IN=" 117 | "num.query.opcode.QUERY=" 118 | "num.query.tcp=" 119 | "num.query.tcpout=" 120 | "num.query.tls=" 121 | "num.query.tls.resume=" 122 | "num.query.ipv6=" 123 | "num.query.flags.QR=" 124 | "num.query.flags.AA=" 125 | "num.query.flags.TC=" 126 | "num.query.flags.RD=" 127 | "num.query.flags.RA=" 128 | "num.query.flags.Z=" 129 | "num.query.flags.AD=" 130 | "num.query.flags.CD=" 131 | "num.query.edns.present=" 132 | "num.query.edns.DO=" 133 | "num.answer.rcode.NOERROR=" 134 | "num.answer.rcode.FORMERR=" 135 | "num.answer.rcode.SERVFAIL=" 136 | "num.answer.rcode.NXDOMAIN=" 137 | "num.answer.rcode.NOTIMPL=" 138 | "num.answer.rcode.REFUSED=" 139 | "num.answer.rcode.nodata=" 140 | "num.query.ratelimited=" 141 | "num.answer.secure=" 142 | "num.answer.bogus=" 143 | "num.rrset.bogus=" 144 | "num.query.aggressive.NOERROR=" 145 | "num.query.aggressive.NXDOMAIN=" 146 | "unwanted.queries=" 147 | "unwanted.replies=" 148 | "msg.cache.count=" 149 | "rrset.cache.count=" 150 | "infra.cache.count=" 151 | "key.cache.count=" 152 | "dnscrypt_shared_secret.cache.count=" 153 | "dnscrypt_nonce.cache.count=" 154 | "num.query.dnscrypt.shared_secret.cachemiss=" 155 | "num.query.dnscrypt.replay=" 156 | "num.query.authzone.up=" 157 | "num.query.authzone.down=" 158 | "num.query.subnet=" 159 | "num.query.subnet_cache=" 160 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_measurement_observe.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use unbound_telemetry::{Statistics, Measurement}; 5 | 6 | fuzz_target!(|data: Statistics| { 7 | let _ = Measurement::observe(data).unwrap(); 8 | }); 9 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_text_parser.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use std::str::FromStr; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use unbound_telemetry::Statistics; 7 | 8 | fuzz_target!(|data: String| { 9 | let _ = Statistics::from_str(&data); 10 | }); 11 | -------------------------------------------------------------------------------- /src/bin/server/cli.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::PathBuf; 3 | 4 | #[derive(structopt::StructOpt, Debug, Clone)] 5 | pub struct Common { 6 | /// Address to listen on for HTTP interface 7 | #[structopt(short = "b", long = "bind", default_value = "0.0.0.0:9167", global = true)] 8 | pub bind: SocketAddr, 9 | 10 | /// HTTP path to expose metrics 11 | #[structopt(short = "p", long = "path", default_value = "/metrics", global = true)] 12 | pub path: String, 13 | 14 | /// Set the log level to run under. 15 | /// 16 | /// Possible values are: error, warn, info, debug, trace 17 | #[structopt( 18 | name = "log-level", 19 | short = "l", 20 | long = "log-level", 21 | default_value = "info", 22 | global = true, 23 | parse(try_from_str) 24 | )] 25 | pub log_level: log::Level, 26 | } 27 | 28 | #[derive(structopt::StructOpt, Debug)] 29 | #[structopt(name = "unbound-telemetry")] 30 | pub enum Arguments { 31 | /// Fetch unbound statistics from the shared memory region. 32 | /// 33 | /// Available for UNIX systems only. 34 | #[cfg(unix)] 35 | Shm { 36 | /// Shared memory key (`shm-key` value from the `unbound.conf`) 37 | #[structopt(name = "KEY", short = "k", long = "shm-key", default_value = "11777")] 38 | shm_key: libc::key_t, 39 | #[structopt(flatten)] 40 | common: Common, 41 | }, 42 | /// Fetch unbound statistics from the remote control Unix socket. 43 | /// 44 | /// Available for UNIX systems only. 45 | #[cfg(unix)] 46 | Uds { 47 | /// Local socket path. 48 | #[structopt(name = "socket", long = "control-interface")] 49 | socket: PathBuf, 50 | #[structopt(flatten)] 51 | common: Common, 52 | }, 53 | /// Fetch unbound statistics from the remote control TCP socket. 54 | /// 55 | /// Certificate and key options can be omitted 56 | /// if TLS is disabled for remote control socket. 57 | Tcp { 58 | /// Server certificate file. 59 | #[structopt( 60 | name = "CA_FILE", 61 | long = "server-cert-file", 62 | requires_all = &["CERT_FILE", "KEY_FILE"] 63 | )] 64 | ca: Option, 65 | 66 | /// Server control certificate file. 67 | #[structopt(name = "CERT_FILE", long = "control-cert-file")] 68 | cert: Option, 69 | 70 | /// Control client private key. 71 | #[structopt(name = "KEY_FILE", long = "control-key-file")] 72 | key: Option, 73 | 74 | /// TLS socket hostname. 75 | #[structopt(name = "interface", long = "control-interface", default_value = "127.0.0.1:8953")] 76 | // Note that at this point we are not using `SocketAddr` type, 77 | // because we might need to do the DNS resolving later. 78 | interface: String, 79 | 80 | #[structopt(flatten)] 81 | common: Common, 82 | }, 83 | } 84 | 85 | impl Arguments { 86 | // Common settings are accessed via method, 87 | // because neither `clap` or `structopt` right now 88 | // are not allowing to create "global" arguments, 89 | // which will be shared among all subcommands. 90 | pub fn common(&self) -> &Common { 91 | match self { 92 | Arguments::Tcp { common, .. } => common, 93 | #[cfg(unix)] 94 | Arguments::Shm { common, .. } => common, 95 | #[cfg(unix)] 96 | Arguments::Uds { common, .. } => common, 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/bin/server/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use structopt::StructOpt; 4 | 5 | mod cli; 6 | mod server; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), Box> { 10 | let config = self::cli::Arguments::from_args(); 11 | simple_logger::init_with_level(config.common().log_level)?; 12 | self::server::serve(config).await?; 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /src/bin/server/server.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io; 3 | use std::sync::Arc; 4 | use std::time; 5 | 6 | use hyper::service::{make_service_fn, service_fn}; 7 | use hyper::{header::HeaderValue, Body, Method, Request, Response, Server, StatusCode}; 8 | use unbound_telemetry::{Measurement, RemoteControlSource, Source, TextTransport, TlsTransport}; 9 | #[cfg(unix)] 10 | use unbound_telemetry::{SharedMemorySource, UdsTransport}; 11 | 12 | use crate::cli; 13 | 14 | static INDEX_BODY: &str = include_str!("../../../assets/index.html"); 15 | 16 | struct Context { 17 | config: cli::Arguments, 18 | source: Box, 19 | } 20 | 21 | async fn handler(req: Request, context: Arc) -> hyper::Result> { 22 | match (req.method(), req.uri().path()) { 23 | // Landing page 24 | (&Method::GET, "/") => Ok(Response::new(Body::from(INDEX_BODY))), 25 | // Observing statistics 26 | (&Method::GET, path) if path == context.config.common().path => { 27 | let start = time::Instant::now(); 28 | let observation = context.source.observe().await; 29 | let elapsed = time::Instant::now().duration_since(start); 30 | 31 | let mut response = observation 32 | .and_then(|statistics| { 33 | let mut m = Measurement::observe(statistics)?; 34 | 35 | // These two metrics are not related directly to the unbound, 36 | // but we want to provide some extra data 37 | m.counter("up", "This Unbound instance is up and running").set(1)?; 38 | m.gauge("scrape_duration_seconds", "Time spent on metrics scraping") 39 | .set(elapsed)?; 40 | 41 | Ok(m.drain()) 42 | }) 43 | .map(|body| Response::new(Body::from(body))) 44 | .or_else::(|e| Ok(render_error(e))) 45 | .unwrap_or_else(|_| unreachable!("Err variant is excluded by the combinators chain")); 46 | 47 | response 48 | .headers_mut() 49 | .insert("Content-Type", HeaderValue::from_static("text/plain")); 50 | 51 | Ok(response) 52 | } 53 | 54 | // Healthcheck 55 | (method, "/healthcheck") if method == Method::HEAD || method == Method::GET => { 56 | match context.source.healthcheck().await { 57 | Ok(_) => { 58 | log::debug!("Health check completed successfully"); 59 | Ok(Response::new(Body::empty())) 60 | } 61 | Err(e) => Ok(render_error(e)), 62 | } 63 | } 64 | 65 | // Fallback for any other requests, results in plain 404 66 | _ => { 67 | let mut resp = Response::new(Body::empty()); 68 | *resp.status_mut() = StatusCode::NOT_FOUND; 69 | Ok(resp) 70 | } 71 | } 72 | } 73 | 74 | pub async fn serve(config: cli::Arguments) -> Result<(), Box> { 75 | let server_config = (*config.common()).clone(); 76 | let source = build_source(&config)?; 77 | 78 | let context = Arc::new(Context { config, source }); 79 | let service = make_service_fn(move |_| { 80 | let handler_context = context.clone(); 81 | 82 | async { 83 | let f = service_fn(move |req| handler(req, handler_context.clone())); 84 | 85 | Ok::<_, hyper::Error>(f) 86 | } 87 | }); 88 | 89 | let ctrl_c = tokio::signal::ctrl_c(); 90 | let server = Server::bind(&server_config.bind) 91 | .serve(service) 92 | .with_graceful_shutdown(async { 93 | let _ = ctrl_c.await; 94 | }); 95 | log::info!("Listening on {}", server_config.bind); 96 | server.await?; 97 | 98 | Ok(()) 99 | } 100 | 101 | fn render_error(e: T) -> Response { 102 | log::error!("Unable to observe unbound statistics: {}", e); 103 | 104 | let body = Body::from(format!("# Unable to observe unbound statistics: {}", e)); 105 | let mut response = Response::new(body); 106 | *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; 107 | 108 | response 109 | } 110 | 111 | fn build_source(config: &cli::Arguments) -> io::Result> { 112 | let source = match config { 113 | cli::Arguments::Tcp { 114 | ca: Some(ca), 115 | cert: Some(cert), 116 | key: Some(key), 117 | interface, 118 | .. 119 | } => { 120 | let transport = TlsTransport::new(ca, cert, key, interface.clone())?; 121 | let source = RemoteControlSource::new(transport); 122 | Box::new(source) as Box<_> 123 | } 124 | cli::Arguments::Tcp { 125 | ca: None, 126 | cert: None, 127 | key: None, 128 | interface, 129 | .. 130 | } => { 131 | let transport = TextTransport::new(interface.clone())?; 132 | let source = RemoteControlSource::new(transport); 133 | 134 | Box::new(source) as Box<_> 135 | } 136 | cli::Arguments::Tcp { .. } => unreachable!("CLI validation should handle this case"), 137 | #[cfg(unix)] 138 | cli::Arguments::Uds { socket, .. } => { 139 | let transport = UdsTransport::new(socket); 140 | let source = RemoteControlSource::new(transport); 141 | Box::new(source) as Box<_> 142 | } 143 | #[cfg(unix)] 144 | cli::Arguments::Shm { shm_key, .. } => Box::new(SharedMemorySource::new(*shm_key)) as Box<_>, 145 | }; 146 | 147 | Ok(source) 148 | } 149 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | unused, 3 | unused_imports, 4 | unused_features, 5 | bare_trait_objects, 6 | future_incompatible, 7 | nonstandard_style, 8 | dead_code, 9 | deprecated 10 | )] 11 | #![warn( 12 | trivial_casts, 13 | trivial_numeric_casts, 14 | unused_extern_crates, 15 | unused_import_braces, 16 | unused_results 17 | )] 18 | 19 | #[macro_use] 20 | mod macros; 21 | mod metrics; 22 | mod sources; 23 | pub mod statistics; 24 | 25 | pub use self::metrics::Measurement; 26 | pub use self::sources::{RemoteControlSource, Source, TextTransport, TlsTransport}; 27 | #[cfg(unix)] 28 | pub use self::sources::{SharedMemorySource, UdsTransport}; 29 | pub use self::statistics::{ParseError, Statistics}; 30 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Your usual enum_primitive macro. 2 | #[macro_export] 3 | macro_rules! super_enum { 4 | ( 5 | enum $name:ident { 6 | $($variant:ident => ($int:expr, $str:expr),)* 7 | } 8 | ) => { 9 | #[derive(Debug, PartialEq, Eq, Hash)] 10 | pub enum $name { 11 | $($variant,)* 12 | } 13 | 14 | impl $name { 15 | // pub fn as_str(&self) -> &'static str { 16 | // match self { 17 | // $($name::$variant => $str,)* 18 | // } 19 | // } 20 | // 21 | // pub fn as_u64(&self) -> u64 { 22 | // match self { 23 | // $($name::$variant => $int,)* 24 | // } 25 | // } 26 | } 27 | 28 | impl std::str::FromStr for $name { 29 | type Err = (); 30 | 31 | fn from_str(s: &str) -> Result { 32 | match () { 33 | $( 34 | _ if s.eq_ignore_ascii_case(stringify!($variant)) => Ok($name::$variant), 35 | )* 36 | _ => Err(()), 37 | } 38 | } 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/metrics/mod.rs: -------------------------------------------------------------------------------- 1 | //! Prometheus metrics format implementation. 2 | //! 3 | //! ## Motivation 4 | //! 5 | //! Currently existing Prometheus crates (`metrics`, `prometheus`, or `opentelemetry`) 6 | //! lacks the ability to collect the measurements from the so-called const metrics 7 | //! (where data is provided from external source and should be set directly as metric value, 8 | //! instead of using something like `Counter::add`). 9 | //! 10 | //! In addition, there is no need to store these values somewhere in a global registry, 11 | //! because they are ephemeral: they are representing system state at the point 12 | //! when request were received and we will not need them later. 13 | //! 14 | //! Since Prometheus text format is quite simple, it is easier to re-implement it 15 | //! and do a quick and dirty writes directly into the output buffer. 16 | 17 | use std::io; 18 | 19 | mod observe; 20 | mod value; 21 | 22 | use self::value::MetricValue; 23 | 24 | #[must_use] 25 | #[derive(Default)] 26 | pub struct Measurement(Vec); 27 | 28 | impl Measurement { 29 | pub fn with_buffer_capacity(capacity: usize) -> Self { 30 | Self(Vec::with_capacity(capacity)) 31 | } 32 | 33 | pub fn counter(&mut self, name: &'static str, help: &'static str) -> MetricGuard<'_, Vec> { 34 | MetricGuard::new(&mut self.0, name, "counter", help) 35 | } 36 | 37 | pub fn gauge(&mut self, name: &'static str, help: &'static str) -> MetricGuard<'_, Vec> { 38 | MetricGuard::new(&mut self.0, name, "gauge", help) 39 | } 40 | 41 | pub fn histogram(&mut self, name: &'static str, help: &'static str) -> HistogramGuard<'_, Vec> { 42 | HistogramGuard::new(&mut self.0, name, help) 43 | } 44 | 45 | pub fn drain(self) -> Vec { 46 | self.0 47 | } 48 | } 49 | 50 | #[must_use] 51 | pub struct MetricGuard<'t, T> 52 | where 53 | T: io::Write + 't, 54 | { 55 | w: &'t mut T, 56 | name: &'static str, 57 | // metric kind and help text 58 | header: Option<(&'static str, &'static str)>, 59 | } 60 | 61 | impl<'t, T> MetricGuard<'t, T> 62 | where 63 | T: io::Write + 't, 64 | { 65 | pub fn new(w: &'t mut T, name: &'static str, kind: &'static str, help: &'static str) -> Self { 66 | Self { 67 | w, 68 | name, 69 | header: Some((kind, help)), 70 | } 71 | } 72 | 73 | pub fn set(&mut self, value: V) -> io::Result<&mut Self> 74 | where 75 | V: MetricValue, 76 | { 77 | self.ensure_header()?; 78 | 79 | self.w.write_fmt(format_args!("unbound_{} ", self.name))?; 80 | value.write(&mut self.w)?; 81 | self.w.write_all(b"\n")?; 82 | 83 | Ok(self) 84 | } 85 | 86 | pub fn set_with_label(&mut self, key: &'static str, label: L, value: V) -> io::Result<&mut Self> 87 | where 88 | L: MetricValue, 89 | V: MetricValue, 90 | { 91 | self.ensure_header()?; 92 | 93 | self.w.write_fmt(format_args!("unbound_{}{{{}=\"", self.name, key))?; 94 | label.write(&mut self.w)?; 95 | self.w.write_all(b"\"} ")?; 96 | value.write(&mut self.w)?; 97 | self.w.write_all(b"\n")?; 98 | 99 | Ok(self) 100 | } 101 | 102 | pub fn needs_header(&mut self, value: bool) -> &mut Self { 103 | if !value { 104 | let _ = self.header.take(); 105 | } 106 | 107 | self 108 | } 109 | 110 | fn ensure_header(&mut self) -> io::Result<()> { 111 | match self.header.take() { 112 | Some((kind, help)) => self.w.write_fmt(format_args!( 113 | "# TYPE unbound_{name} {kind}\n# HELP unbound_{name} {help}\n", 114 | name = self.name, 115 | kind = kind, 116 | help = help 117 | )), 118 | None => Ok(()), 119 | } 120 | } 121 | } 122 | 123 | #[must_use] 124 | pub struct HistogramGuard<'t, T> 125 | where 126 | T: io::Write + 't, 127 | { 128 | w: &'t mut T, 129 | name: &'static str, 130 | // metric help text 131 | header: Option<&'static str>, 132 | } 133 | 134 | impl<'t, T> HistogramGuard<'t, T> 135 | where 136 | T: io::Write + 't, 137 | { 138 | pub fn new(w: &'t mut T, name: &'static str, help: &'static str) -> Self { 139 | Self { 140 | w, 141 | name, 142 | header: Some(help), 143 | } 144 | } 145 | 146 | pub fn bucket(&mut self, le: L, value: V) -> io::Result<&mut Self> 147 | where 148 | L: MetricValue, 149 | V: MetricValue, 150 | { 151 | self.ensure_header()?; 152 | 153 | self.w.write_fmt(format_args!("unbound_{}_bucket{{le=\"", self.name))?; 154 | le.write(&mut self.w)?; 155 | self.w.write_all(b"\"} ")?; 156 | value.write(&mut self.w)?; 157 | self.w.write_all(b"\n")?; 158 | 159 | Ok(self) 160 | } 161 | 162 | pub fn sum(&mut self, value: V) -> io::Result<&mut Self> 163 | where 164 | V: MetricValue, 165 | { 166 | self.ensure_header()?; 167 | 168 | self.w.write_fmt(format_args!("unbound_{}_sum ", self.name))?; 169 | value.write(&mut self.w)?; 170 | self.w.write_all(b"\n")?; 171 | 172 | Ok(self) 173 | } 174 | 175 | pub fn count(&mut self, value: V) -> io::Result<&mut Self> 176 | where 177 | V: MetricValue, 178 | { 179 | self.ensure_header()?; 180 | 181 | self.w.write_fmt(format_args!("unbound_{}_count ", self.name))?; 182 | value.write(&mut self.w)?; 183 | self.w.write_all(b"\n")?; 184 | 185 | Ok(self) 186 | } 187 | 188 | fn ensure_header(&mut self) -> io::Result<()> { 189 | match self.header.take() { 190 | Some(help) => self.w.write_fmt(format_args!( 191 | "# TYPE unbound_{name} histogram\n# HELP unbound_{name} {help}\n", 192 | name = self.name, 193 | help = help 194 | )), 195 | None => Ok(()), 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/metrics/observe.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use super::Measurement; 4 | use crate::statistics::{Bucket, Statistics}; 5 | 6 | impl Measurement { 7 | #[allow(unused_results)] // `.set` and `.set_with_label` are returning a lot of references. 8 | pub fn observe(mut s: Statistics) -> io::Result { 9 | // Roughly equal to the response body size plus some extra capacity 10 | let mut w = Measurement::with_buffer_capacity(16_834); 11 | 12 | // Common 13 | w.gauge("num_threads", "The number of threads to create to serve clients") 14 | .set(s.threads.len())?; 15 | 16 | // Time 17 | w.counter("time_up_seconds_total", "Uptime since server boot in seconds") 18 | .set(s.time.up)?; 19 | w.counter("time_now_seconds", "Current time in seconds since UNIX epoch") 20 | .set(s.time.now)?; 21 | w.counter("time_elapsed_seconds", "Time since last statistics printout in seconds") 22 | .set(s.time.elapsed)?; 23 | 24 | // Memory caches 25 | w.gauge("memory_caches_bytes", "Memory in bytes in use by caches") 26 | .set_with_label("cache", "rrset", s.cache.rrset)? 27 | .set_with_label("cache", "message", s.cache.message)? 28 | .set_with_label("cache", "dnscrypt_shared_secret", s.cache.dnscrypt_shared_secret)? 29 | .set_with_label("cache", "dnscrypt_nonce", s.cache.dnscrypt_nonce)?; 30 | 31 | // Memory modules 32 | w.gauge("memory_modules_bytes", "Memory in bytes in use by modules") 33 | .set_with_label("module", "iterator", s.modules.iterator)? 34 | .set_with_label("module", "validator", s.modules.validator)? 35 | .set_with_label("module", "respip", s.modules.respip)? 36 | .set_with_label("module", "subnet", s.modules.subnet)?; 37 | // TODO: 38 | // .set_with_label("module", "ipsecmod", s.modules.ipsecmod)? 39 | 40 | w.gauge("memory_http_bytes", "Memory in bytes in use by HTTP/2 queries") 41 | .set_with_label("http", "query_buffer", s.http.query_buffer)? 42 | .set_with_label("http", "response_buffer", s.http.response_buffer)?; 43 | 44 | // Mem buffers 45 | w.gauge( 46 | "memory_stream_wait_count", 47 | "The number of bytes in the stream wait buffers", 48 | ) 49 | .set(s.mem_streamwait)?; 50 | 51 | w.counter( 52 | "query_tcp_total", 53 | "Total number of queries that were made using TCP towards the server", 54 | ) 55 | .set(s.num_query_tcp)?; 56 | w.counter( 57 | "query_tcp_out_total", 58 | "Total number of queries that were made using TCP outwards the server", 59 | ) 60 | .set(s.num_query_tcp_out)?; 61 | w.counter( 62 | "query_tls_total", 63 | "Total number of queries that were made using TLS towards the server", 64 | ) 65 | .set(s.num_query_tls)?; 66 | w.counter( 67 | "query_tls_resume_total", 68 | "Total number of queries that were made using TLS resumption", 69 | ) 70 | .set(s.num_query_tls_resume)?; 71 | w.counter( 72 | "query_ipv6_total", 73 | "Total number of queries that were made using IPv6 toward the server", 74 | ) 75 | .set(s.num_query_ipv6)?; 76 | w.counter( 77 | "query_https_total", 78 | "Total number of queries that were made using HTTPS", 79 | ) 80 | .set(s.num_query_https)?; 81 | 82 | // Query EDNS numbers 83 | w.counter( 84 | "query_edns_DO_total", 85 | "Total number of queries that had an EDNS OPT record with the DO (DNSSEC OK) bit set present", 86 | ) 87 | .set(s.num_query_edns_do)?; 88 | w.counter( 89 | "query_edns_present_total", 90 | "Total number of queries that had an EDNS OPT record present", 91 | ) 92 | .set(s.num_query_edns_present)?; 93 | // Query iteration numbers 94 | w.counter( 95 | "query_ratelimited_total", 96 | "Total number of queries that had been rate limited", 97 | ) 98 | .set(s.num_query_rate_limited)?; 99 | 100 | // Query validation numbers 101 | w.counter("answers_secure_total", "Total amount of answers that were secure (AD)") 102 | .set(s.num_answer_secure)?; 103 | // Deprecated version to maintain compatibility 104 | w.counter( 105 | "answers_bogus", 106 | "Total amount of answers that were bogus (withheld as SERVFAIL)", 107 | ) 108 | .set(s.num_answer_bogus)?; 109 | w.counter( 110 | "answers_bogus_total", 111 | "Total amount of answers that were bogus (withheld as SERVFAIL)", 112 | ) 113 | .set(s.num_answer_bogus)?; 114 | 115 | w.counter( 116 | "rrset_bogus_total", 117 | "Total number of rrsets marked bogus by the validator", 118 | ) 119 | .set(s.num_rrset_bogus)?; 120 | 121 | // Cache count (deprecated, exposed only to maintain compatibility with `kumina/unbound_exporter`) 122 | w.gauge("msg_cache_count", "The number of messages cached") 123 | .set(s.cache_count.message)?; 124 | w.gauge("rrset_cache_count", "The number of rrset cached") 125 | .set(s.cache_count.rrset)?; 126 | 127 | // Cache count (new version) 128 | w.gauge("cache_count_total", "The number of cached entries") 129 | .set_with_label("type", "message", s.cache_count.message)? 130 | .set_with_label("type", "rrset", s.cache_count.rrset)? 131 | .set_with_label("type", "key", s.cache_count.key)? 132 | .set_with_label("type", "infra", s.cache_count.infra)? 133 | .set_with_label("type", "dnscrypt_nonce", s.cache_count.dnscrypt_nonce)? 134 | .set_with_label("type", "dnscrypt_shared_secret", s.cache_count.dnscrypt_shared_secret)?; 135 | 136 | w.counter( 137 | "unwanted_queries_total", 138 | "Total number of queries that were refused or dropped because they failed the access control settings.", 139 | ) 140 | .set(s.num_unwanted_queries)?; 141 | w.counter( 142 | "unwanted_replies_total", 143 | "Total number of replies that were unwanted or unsolicited", 144 | ) 145 | .set(s.num_unwanted_replies)?; 146 | 147 | let mut answer_rcodes = w.counter( 148 | "answer_rcodes_total", 149 | "Total number of answers to queries, from cache or from recursion, by response code.", 150 | ); 151 | for (rcode, value) in s.answer_rcodes.iter() { 152 | answer_rcodes.set_with_label("rcode", rcode, value)?; 153 | } 154 | let mut query_opcodes = w.counter( 155 | "query_opcodes_total", 156 | "Total number of queries with a given query opcode", 157 | ); 158 | for (opcode, value) in s.query_opcodes.iter() { 159 | query_opcodes.set_with_label("opcode", opcode, value)?; 160 | } 161 | let mut query_types = w.counter("query_types_total", "Total number of queries with a given query type"); 162 | query_types.set_with_label("type", "other", s.query_types_other)?; 163 | for (rtype, value) in s.query_types.iter() { 164 | query_types.set_with_label("type", rtype, value)?; 165 | } 166 | let mut query_classes = w.counter( 167 | "query_classes_total", 168 | "Total number of queries with a given query class", 169 | ); 170 | query_classes.set_with_label("class", "other", s.query_classes_other)?; 171 | for (class, value) in s.query_classes.iter() { 172 | query_classes.set_with_label("class", class, value)?; 173 | } 174 | w.counter( 175 | "query_flags_total", 176 | "Total number of queries that had a given flag set in the header", 177 | ) 178 | .set_with_label("flag", "QR", s.flags.qr)? 179 | .set_with_label("flag", "AA", s.flags.aa)? 180 | .set_with_label("flag", "TC", s.flags.tc)? 181 | .set_with_label("flag", "RD", s.flags.rd)? 182 | .set_with_label("flag", "RA", s.flags.ra)? 183 | .set_with_label("flag", "Z", s.flags.z)? 184 | .set_with_label("flag", "AD", s.flags.ad)? 185 | .set_with_label("flag", "CD", s.flags.cd)?; 186 | 187 | // Histogram 188 | let mut hist = w.histogram("response_time_seconds", "Query response time in seconds"); 189 | hist.sum(s.histogram.sum())?.count(s.histogram.count())?; 190 | for bucket in s.histogram.buckets() { 191 | match bucket { 192 | Bucket::Le(le, value) => hist.bucket(le, value)?, 193 | Bucket::Inf(value) => hist.bucket("+Inf", value)?, 194 | }; 195 | } 196 | 197 | // threads 198 | for (idx, thread) in s.threads.iter().enumerate() { 199 | let add_header = idx == 0; 200 | 201 | // Queries 202 | w.counter("queries_total", "Total number of queries received") 203 | .needs_header(add_header) 204 | .set_with_label("thread", idx, thread.num_queries)?; 205 | w.counter( 206 | "queries_ip_ratelimited_total", 207 | "Total number of queries rate limited by IP", 208 | ) 209 | .needs_header(add_header) 210 | .set_with_label("thread", idx, thread.num_queries_ip_ratelimited)?; 211 | w.counter( 212 | "cache_hits_total", 213 | "Total number of queries that were successfully answered using a cache lookup.", 214 | ) 215 | .needs_header(add_header) 216 | .set_with_label("thread", idx, thread.num_cache_hits)?; 217 | w.counter( 218 | "cache_misses_total", 219 | "Total number of cache queries that needed recursive processing.", 220 | ) 221 | .needs_header(add_header) 222 | .set_with_label("thread", idx, thread.num_cache_miss)?; 223 | w.counter("prefetches_total", "Total number of cache prefetches performed") 224 | .needs_header(add_header) 225 | .set_with_label("thread", idx, thread.num_prefetch)?; 226 | 227 | // Deprecated since unbound version 1.10.1 228 | w.counter( 229 | "zero_ttl_responses_total", 230 | "Total number of replies with ttl zero, because they served an expired cache entry.", 231 | ) 232 | .needs_header(add_header) 233 | .set_with_label("thread", idx, thread.num_zero_ttl)?; 234 | // Added since unbound version 1.10.1 235 | w.counter( 236 | "expired_responses_total", 237 | "Total number of replies that served an expired cache entry.", 238 | ) 239 | .needs_header(add_header) 240 | .set_with_label("thread", idx, thread.num_zero_ttl)?; 241 | 242 | // TODO:! 243 | // w.counter("recursive_replies_total","Total number of replies sent to queries that needed recursive processing") 244 | // .set_with_label("thread", idx, thread.mesh_replies_sent)?; 245 | 246 | // DNSCrypt 247 | w.counter( 248 | "dnscrypt_valid_queries_total", 249 | "Total number of queries that were encrypted and successfully decapsulated by dnscrypt", 250 | ) 251 | .needs_header(add_header) 252 | .set_with_label("thread", idx, thread.num_dnscrypt_crypted)?; 253 | w.counter( 254 | "dnscrypt_cert_queries_total", 255 | "Total number of queries that were requesting dnscrypt certificates", 256 | ) 257 | .needs_header(add_header) 258 | .set_with_label("thread", idx, thread.num_dnscrypt_cert)?; 259 | w.counter("dnscrypt_cleartext_queries_total", "Total number of queries received on dnscrypt port that were cleartext and not a request for certificates") 260 | .needs_header(add_header) 261 | .set_with_label("thread", idx,thread.num_dnscrypt_cleartext)?; 262 | w.counter( 263 | "dnscrypt_malformed_queries_total", 264 | "Total number of requests that were neither cleartext, not valid dnscrypt messages", 265 | ) 266 | .needs_header(add_header) 267 | .set_with_label("thread", idx, thread.num_dnscrypt_malformed)?; 268 | 269 | // Request list 270 | w.gauge( 271 | "request_list_current_all", 272 | "Current size of the request list, including internally generated queries", 273 | ) 274 | .needs_header(add_header) 275 | .set_with_label("thread", idx, thread.requestlist_current_all)?; 276 | w.gauge( 277 | "request_list_current_user", 278 | "Current size of the request list, only counting the requests from client queries", 279 | ) 280 | .needs_header(add_header) 281 | .set_with_label("thread", idx, thread.requestlist_current_user)?; 282 | // TODO: 283 | w.counter( 284 | "request_list_exceeded_total", 285 | "Number of queries that were dropped because the request list was full", 286 | ) 287 | .needs_header(add_header) 288 | .set_with_label("thread", idx, thread.requestlist_exceeded)?; 289 | w.counter( 290 | "request_list_overwritten_total", 291 | "Total number of requests in the request list that were overwritten by newer entries", 292 | ) 293 | .needs_header(add_header) 294 | .set_with_label("thread", idx, thread.requestlist_overwritten)?; 295 | 296 | // Recursion 297 | w.gauge("recursion_time_seconds_avg", "Average time it took to answer queries that needed recursive processing (does not include in-cache requests)") 298 | .needs_header(add_header) 299 | .set_with_label("thread", idx, thread.recursion_time_avg)?; 300 | w.gauge( 301 | "recursion_time_seconds_median", 302 | "The median of the time it took to answer queries that needed recursive processing", 303 | ) 304 | .needs_header(add_header) 305 | .set_with_label("thread", idx, thread.recursion_time_median)?; 306 | 307 | // TCP usage 308 | w.gauge( 309 | "tcp_usage_current", 310 | "Number of the currently held TCP buffers for incoming connections", 311 | ) 312 | .needs_header(add_header) 313 | .set_with_label("thread", idx, thread.tcp_usage)?; 314 | } 315 | 316 | Ok(w) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/metrics/value.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::Error; 3 | use std::time; 4 | 5 | use domain::base::iana::{Class, Opcode, Rcode, Rtype}; 6 | 7 | pub trait MetricValue { 8 | fn write(self, w: T) -> io::Result<()> 9 | where 10 | T: io::Write; 11 | } 12 | 13 | impl MetricValue for u64 { 14 | fn write(self, w: T) -> io::Result<()> 15 | where 16 | T: io::Write, 17 | { 18 | itoa::write(w, self).map(|_| ()) 19 | } 20 | } 21 | impl MetricValue for i32 { 22 | fn write(self, w: T) -> io::Result<()> 23 | where 24 | T: io::Write, 25 | { 26 | itoa::write(w, self).map(|_| ()) 27 | } 28 | } 29 | 30 | impl MetricValue for usize { 31 | fn write(self, w: T) -> io::Result<()> 32 | where 33 | T: io::Write, 34 | { 35 | itoa::write(w, self).map(|_| ()) 36 | } 37 | } 38 | 39 | impl MetricValue for f64 { 40 | fn write(self, w: T) -> io::Result<()> 41 | where 42 | T: io::Write, 43 | { 44 | dtoa::write(w, self).map(|_| ()) 45 | } 46 | } 47 | 48 | impl MetricValue for time::Duration { 49 | fn write(self, w: T) -> io::Result<()> 50 | where 51 | T: io::Write, 52 | { 53 | dtoa::write(w, self.as_secs_f64()).map(|_| ()) 54 | } 55 | } 56 | 57 | impl MetricValue for &str { 58 | fn write(self, mut w: T) -> io::Result<()> 59 | where 60 | T: io::Write, 61 | { 62 | w.write_all(self.as_bytes()) 63 | } 64 | } 65 | 66 | impl MetricValue for Class { 67 | fn write(self, mut w: T) -> io::Result<()> 68 | where 69 | T: io::Write, 70 | { 71 | let res = match self { 72 | Class::Any => w.write(b"ANY"), 73 | other => { 74 | let str = format!("{}", other); 75 | 76 | w.write(str.as_bytes()) 77 | } 78 | }; 79 | 80 | res.map(|_| ()) 81 | } 82 | } 83 | 84 | impl MetricValue for Opcode { 85 | fn write(self, mut w: T) -> io::Result<()> 86 | where 87 | T: io::Write, 88 | { 89 | let str = format!("{}", self); 90 | 91 | w.write(str.as_bytes()).map(|_| ()) 92 | } 93 | } 94 | 95 | impl MetricValue for Rcode { 96 | fn write(self, mut w: T) -> io::Result<()> 97 | where 98 | T: io::Write, 99 | { 100 | let str = format!("{}", self); 101 | 102 | w.write(str.as_bytes()).map(|_| ()) 103 | } 104 | } 105 | 106 | impl MetricValue for Rtype { 107 | fn write(self, mut w: T) -> io::Result<()> 108 | where 109 | T: io::Write, 110 | { 111 | let str = format!("{}", self); 112 | 113 | w.write(str.as_bytes()).map(|_| ()) 114 | } 115 | } 116 | 117 | impl MetricValue for &V 118 | where 119 | V: MetricValue + Copy, 120 | { 121 | fn write(self, w: T) -> Result<(), Error> 122 | where 123 | T: io::Write, 124 | { 125 | (*self).write(w) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/sources/control/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::marker::Unpin; 3 | use std::str::FromStr; 4 | 5 | use super::Source; 6 | use crate::Statistics; 7 | use tokio::prelude::{AsyncRead, AsyncWrite, *}; 8 | 9 | mod text; 10 | mod tls; 11 | #[cfg(unix)] 12 | mod uds; 13 | 14 | pub use self::text::TextTransport; 15 | pub use self::tls::TlsTransport; 16 | #[cfg(unix)] 17 | pub use self::uds::UdsTransport; 18 | 19 | #[async_trait::async_trait] 20 | pub trait RemoteControlTransport: Sized + Send + Sync { 21 | type Socket: AsyncRead + AsyncWrite + Send + Unpin; 22 | 23 | async fn connect(&self) -> io::Result; 24 | } 25 | 26 | pub struct RemoteControlSource { 27 | transport: T, 28 | } 29 | 30 | impl RemoteControlSource { 31 | pub fn new(transport: T) -> Self { 32 | Self { transport } 33 | } 34 | } 35 | 36 | #[async_trait::async_trait] 37 | impl Source for RemoteControlSource 38 | where 39 | T: RemoteControlTransport, 40 | { 41 | async fn healthcheck(&self) -> io::Result<()> { 42 | let _ = self.transport.connect().await?; 43 | 44 | Ok(()) 45 | } 46 | 47 | async fn observe(&self) -> io::Result { 48 | let mut socket = self.transport.connect().await?; 49 | 50 | socket.write_all(b"UBCT1 stats_noreset\n").await?; 51 | let mut buffer = String::new(); 52 | 53 | let _ = socket.read_to_string(&mut buffer).await?; 54 | 55 | Statistics::from_str(&buffer).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/sources/control/text.rs: -------------------------------------------------------------------------------- 1 | //! Data source which receives statistics via TLS socket. 2 | 3 | use std::io; 4 | 5 | use tokio::net::TcpStream; 6 | 7 | use super::RemoteControlTransport; 8 | 9 | pub struct TextTransport { 10 | host: String, 11 | } 12 | 13 | impl TextTransport { 14 | pub fn new(host: String) -> io::Result { 15 | Ok(TextTransport { host }) 16 | } 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl RemoteControlTransport for TextTransport { 21 | type Socket = TcpStream; 22 | 23 | async fn connect(&self) -> io::Result { 24 | TcpStream::connect(&self.host).await 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/sources/control/tls.rs: -------------------------------------------------------------------------------- 1 | //! Data source which receives statistics via TLS socket. 2 | 3 | use std::fs; 4 | use std::io; 5 | use std::path::Path; 6 | 7 | use native_tls::{Certificate, Identity, TlsConnector as NativeTlsConnector}; 8 | use tokio::net::TcpStream; 9 | use tokio_tls::{TlsConnector, TlsStream}; 10 | 11 | use super::RemoteControlTransport; 12 | 13 | pub struct TlsTransport { 14 | connector: TlsConnector, 15 | host: String, 16 | } 17 | 18 | impl TlsTransport { 19 | pub fn new(ca: impl AsRef, cert: impl AsRef, key: impl AsRef, host: String) -> io::Result { 20 | let ca = fs::read(ca)?; 21 | let ca = Certificate::from_pem(&ca).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 22 | 23 | let cert = fs::read(cert)?; 24 | let key = fs::read(key)?; 25 | 26 | let identity = Identity::from_pkcs8(&cert, &key).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 27 | 28 | let connector = NativeTlsConnector::builder() 29 | .add_root_certificate(ca) 30 | .identity(identity) 31 | .build() 32 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 33 | 34 | Ok(TlsTransport { 35 | connector: TlsConnector::from(connector), 36 | host, 37 | }) 38 | } 39 | } 40 | 41 | #[async_trait::async_trait] 42 | impl RemoteControlTransport for TlsTransport { 43 | type Socket = TlsStream; 44 | 45 | async fn connect(&self) -> io::Result { 46 | let socket = TcpStream::connect(&self.host).await?; 47 | 48 | let stream = self 49 | .connector 50 | .connect("unbound", socket) 51 | .await 52 | .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?; 53 | 54 | Ok(stream) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/sources/control/uds.rs: -------------------------------------------------------------------------------- 1 | //! Data source which receives statistics via Unix Domain Socket. 2 | 3 | use std::io; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use tokio::net::UnixStream; 7 | 8 | use super::RemoteControlTransport; 9 | 10 | #[derive(Debug)] 11 | pub struct UdsTransport { 12 | path: PathBuf, 13 | } 14 | 15 | impl UdsTransport { 16 | pub fn new(path: impl AsRef) -> UdsTransport { 17 | Self { 18 | path: path.as_ref().to_path_buf(), 19 | } 20 | } 21 | } 22 | 23 | #[async_trait::async_trait] 24 | impl RemoteControlTransport for UdsTransport { 25 | type Socket = UnixStream; 26 | 27 | async fn connect(&self) -> io::Result { 28 | UnixStream::connect(&self.path).await 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/sources/memory/ffi.rs: -------------------------------------------------------------------------------- 1 | /// Structs which are exposed from unbound via shared memory. 2 | /// 3 | /// These are Rust versions of the C structs defined at `libunbound/unbound.h` 4 | 5 | // Following constants were copied from the `libunbound/unbound.h` 6 | const UB_STATS_QTYPE_NUM: usize = 256; 7 | const UB_STATS_QCLASS_NUM: usize = 256; 8 | const UB_STATS_RCODE_NUM: usize = 16; 9 | const UB_STATS_OPCODE_NUM: usize = 16; 10 | const UB_STATS_BUCKET_NUM: usize = 40; 11 | 12 | #[repr(C)] 13 | #[derive(Debug)] 14 | pub struct Time { 15 | pub now_sec: libc::c_longlong, 16 | pub now_usec: libc::c_longlong, 17 | pub up_sec: libc::c_longlong, 18 | pub up_usec: libc::c_longlong, 19 | pub elapsed_sec: libc::c_longlong, 20 | pub elapsed_usec: libc::c_longlong, 21 | } 22 | 23 | #[repr(C)] 24 | #[derive(Debug)] 25 | pub struct Memory { 26 | pub msg: libc::c_longlong, 27 | pub rrset: libc::c_longlong, 28 | pub val: libc::c_longlong, 29 | pub iter: libc::c_longlong, 30 | pub subnet: libc::c_longlong, 31 | pub ipsecmod: libc::c_longlong, 32 | pub respip: libc::c_longlong, 33 | pub dnscrypt_shared_secret: libc::c_longlong, 34 | pub dnscrypt_nonce: libc::c_longlong, 35 | } 36 | 37 | /// This struct is shared via the shm segment (`shm-key` from `unbound.conf`), 38 | /// maps to `ub_shm_stat_info` from `libunbound/unbound.h` 39 | #[repr(C)] 40 | #[derive(Debug)] 41 | pub struct ShmStatInfo { 42 | pub num_threads: libc::c_int, 43 | pub time: Time, 44 | pub memory: Memory, 45 | } 46 | 47 | /// Maps to `ub_server_stats` from `libunbound/unbound.h` 48 | #[repr(C)] 49 | pub struct ServerStats { 50 | pub num_queries: libc::c_longlong, 51 | pub num_queries_ip_ratelimited: libc::c_longlong, 52 | pub num_queries_missed_cache: libc::c_longlong, 53 | pub num_queries_prefetch: libc::c_longlong, 54 | pub sum_query_list_size: libc::c_longlong, 55 | pub max_query_list_size: libc::c_longlong, 56 | pub extended: libc::c_int, 57 | pub qtype: [libc::c_longlong; UB_STATS_QTYPE_NUM], 58 | pub qtype_big: libc::c_longlong, 59 | pub qclass: [libc::c_longlong; UB_STATS_QCLASS_NUM], 60 | pub qclass_big: libc::c_longlong, 61 | pub qopcode: [libc::c_longlong; UB_STATS_OPCODE_NUM], 62 | pub qtcp: libc::c_longlong, 63 | pub qtcp_outgoing: libc::c_longlong, 64 | pub qtls: libc::c_longlong, 65 | pub qipv6: libc::c_longlong, 66 | pub qbit_qr: libc::c_longlong, 67 | pub qbit_aa: libc::c_longlong, 68 | pub qbit_tc: libc::c_longlong, 69 | pub qbit_rd: libc::c_longlong, 70 | pub qbit_ra: libc::c_longlong, 71 | pub qbit_z: libc::c_longlong, 72 | pub qbit_ad: libc::c_longlong, 73 | pub qbit_cd: libc::c_longlong, 74 | pub qedns: libc::c_longlong, 75 | pub qedns_do: libc::c_longlong, 76 | pub ans_rcode: [libc::c_longlong; UB_STATS_RCODE_NUM], 77 | pub ans_rcode_nodata: libc::c_longlong, 78 | pub ans_secure: libc::c_longlong, 79 | pub ans_bogus: libc::c_longlong, 80 | pub rrset_bogus: libc::c_longlong, 81 | pub queries_ratelimited: libc::c_longlong, 82 | pub unwanted_replies: libc::c_longlong, 83 | pub unwanted_queries: libc::c_longlong, 84 | pub tcp_accept_usage: libc::c_longlong, 85 | 86 | // TODO: Field was renamed in unbound 1.10.1 87 | // 88 | // - /** answers served from expired cache */ 89 | // - long long zero_ttl_responses; 90 | // + /** expired answers served from cache */ 91 | // + long long ans_expired; 92 | // 93 | // See https://github.com/NLnetLabs/unbound/commit/f7fe95ad7bae690781f9b78ca252a44fc072ca33 94 | pub zero_ttl_responses: libc::c_longlong, 95 | 96 | pub hist: [libc::c_longlong; UB_STATS_BUCKET_NUM], 97 | pub msg_cache_count: libc::c_longlong, 98 | pub rrset_cache_count: libc::c_longlong, 99 | pub infra_cache_count: libc::c_longlong, 100 | pub key_cache_count: libc::c_longlong, 101 | pub num_query_dnscrypt_crypted: libc::c_longlong, 102 | pub num_query_dnscrypt_cert: libc::c_longlong, 103 | pub num_query_dnscrypt_cleartext: libc::c_longlong, 104 | pub num_query_dnscrypt_crypted_malformed: libc::c_longlong, 105 | pub num_query_dnscrypt_secret_missed_cache: libc::c_longlong, 106 | pub shared_secret_cache_count: libc::c_longlong, 107 | pub num_query_dnscrypt_replay: libc::c_longlong, 108 | pub nonce_cache_count: libc::c_longlong, 109 | pub num_query_authzone_up: libc::c_longlong, 110 | pub num_query_authzone_down: libc::c_longlong, 111 | pub num_neg_cache_noerror: libc::c_longlong, 112 | pub num_neg_cache_nxdomain: libc::c_longlong, 113 | pub num_query_subnet: libc::c_longlong, 114 | pub num_query_subnet_cache: libc::c_longlong, 115 | pub mem_stream_wait: libc::c_longlong, 116 | pub qtls_resume: libc::c_longlong, 117 | } 118 | 119 | /// This struct is shared via the shm segment (`shm-key + 1` from `unbound.conf`), 120 | /// maps to `ub_stats_info` from `libunbound/unbound.h` 121 | pub struct StatsInfo { 122 | pub svr: ServerStats, 123 | pub mesh_num_states: libc::c_longlong, 124 | pub mesh_num_reply_states: libc::c_longlong, 125 | pub mesh_jostled: libc::c_longlong, 126 | pub mesh_dropped: libc::c_longlong, 127 | pub mesh_replies_sent: libc::c_longlong, 128 | pub mesh_replies_sum_wait_sec: libc::c_longlong, 129 | pub mesh_replies_sum_wait_usec: libc::c_longlong, 130 | pub mesh_time_median: libc::c_double, 131 | } 132 | -------------------------------------------------------------------------------- /src/sources/memory/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | mod ffi; 4 | mod shm; 5 | mod types; 6 | mod wrappers; 7 | 8 | use self::shm::Segment; 9 | use self::wrappers::SharedMemory; 10 | use super::Source; 11 | use crate::Statistics; 12 | 13 | /// Data source which loads `unbound` statistics via the shared memory. 14 | /// 15 | /// TODO: It feels like shm source is racy right now, as there is no locking anywhere 16 | /// and read data is changed by `unbound` on the fly during the reading 17 | #[derive(Debug, Default)] 18 | pub struct SharedMemorySource { 19 | shm_key: libc::key_t, 20 | } 21 | 22 | impl SharedMemorySource { 23 | pub fn new(shm_key: libc::key_t) -> SharedMemorySource { 24 | SharedMemorySource { shm_key } 25 | } 26 | } 27 | 28 | #[async_trait::async_trait] 29 | impl Source for SharedMemorySource { 30 | async fn healthcheck(&self) -> io::Result<()> { 31 | Segment::::get(self.shm_key).map(|_| ()) 32 | } 33 | 34 | async fn observe(&self) -> io::Result { 35 | let memory = SharedMemory::get(self.shm_key)?; 36 | 37 | Ok(Statistics::from(memory)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/sources/memory/shm.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | use std::ops::Deref; 4 | use std::ptr; 5 | use std::slice; 6 | 7 | /// `SHM_RDONLY` is not defined in `libc` for NetBSD. 8 | /// 9 | /// TODO: Contribute it later 10 | #[cfg(target_os = "netbsd")] 11 | const SHM_RDONLY: libc::c_int = 0o10000; 12 | #[cfg(not(target_os = "netbsd"))] 13 | const SHM_RDONLY: libc::c_int = libc::SHM_RDONLY; 14 | 15 | pub struct Segment { 16 | ptr: *const T, 17 | } 18 | 19 | impl Segment { 20 | /// Attach the shared memory to the current process address space 21 | /// in a read only mode by shm `key` provided. 22 | pub fn get(key: libc::key_t) -> io::Result { 23 | // `shmflg` argument is zero and we are trying to attach 24 | // to the previously created shared memory segment. 25 | let id = unsafe { libc::shmget(key, 0, 0) }; 26 | log::trace!("`shmget({})` call returned the segment id {}", key, id); 27 | if id == -1 { 28 | return Err(io::Error::last_os_error()); 29 | } 30 | 31 | let result = unsafe { libc::shmat(id, ptr::null(), SHM_RDONLY) }; 32 | log::trace!("`shmat({})` call resulted in code {}", id, result as isize); 33 | 34 | if result as isize == -1 { 35 | Err(io::Error::last_os_error()) 36 | } else { 37 | Ok(Segment { 38 | ptr: result as *const T, 39 | }) 40 | } 41 | } 42 | 43 | /// Return slice of `T` from the underline pointer. 44 | /// 45 | /// ## Safety 46 | /// 47 | /// Same issues as with [std::slice::from_raw_parts](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html) 48 | /// 49 | /// TODO: While it is working, is not really cool to do the `Segment::get().as_slice()` 50 | /// it might be better to create two different fabric methods, 51 | /// like `new(key: key_t)` and `slice(key: key_t, len: usize)`? 52 | pub unsafe fn as_slice(&self, len: usize) -> &[T] { 53 | slice::from_raw_parts(self.ptr, len) 54 | } 55 | } 56 | 57 | impl Deref for Segment { 58 | type Target = T; 59 | 60 | fn deref(&self) -> &Self::Target { 61 | unsafe { &*self.ptr } 62 | } 63 | } 64 | 65 | impl Drop for Segment { 66 | fn drop(&mut self) { 67 | let result = unsafe { libc::shmdt(self.ptr as *const libc::c_void) }; 68 | 69 | assert_eq!(result, 0, "Unable to detach shared memory segment"); 70 | } 71 | } 72 | 73 | impl fmt::Debug for Segment 74 | where 75 | T: fmt::Debug, 76 | { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 | f.debug_struct("Segment").field("ptr", self.deref()).finish() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/sources/memory/types.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | // All functions in this module should be replaced with some already existing enums, 4 | // as long as they will provide `from_primitive` and `as_static_str` methods. 5 | 6 | /// Return query record type based on its number. 7 | /// 8 | /// Based on the `rdata_field_descriptors[]` array from the `unbound/rrdef.c`, 9 | /// used to match `ServerStats.qtype` field values to qtype names. 10 | /// 11 | /// Implemented only partially right now with the most popular record types. 12 | /// 13 | /// TODO: Complete the match. 14 | pub fn rr_type(value: usize) -> Option<&'static str> { 15 | let type_ = match value { 16 | 1 => "A", 17 | 2 => "NS", 18 | 5 => "CNAME", 19 | 6 => "SOA", 20 | 10 => "NULL", 21 | 12 => "PTR", 22 | 15 => "MX", 23 | 16 => "TXT", 24 | 24 => "SIG", 25 | 25 => "KEY", 26 | 28 => "AAAA", 27 | 33 => "SRV", 28 | 35 => "NAPTR", 29 | 41 => "OPT", 30 | 43 => "DS", 31 | 44 => "SSHFP", 32 | 46 => "RRSIG", 33 | 47 => "NSEC", 34 | 48 => "DNSKEY", 35 | 50 => "NSEC3", 36 | 51 => "NSEC3PARAM", 37 | 52 => "TLSA", 38 | 61 => "OPENPGPKEY", 39 | 255 => "ANY", 40 | 252 => "AXFR", 41 | 257 => "CAA", 42 | _ => return None, 43 | }; 44 | 45 | Some(type_) 46 | } 47 | 48 | pub fn rr_class(value: usize) -> Option<&'static str> { 49 | let class = match value { 50 | 1 => "IN", 51 | 3 => "CH", 52 | 4 => "HS", 53 | 254 => "NONE", 54 | 255 => "ANY", 55 | _ => return None, 56 | }; 57 | 58 | Some(class) 59 | } 60 | 61 | pub fn rr_opcode(value: usize) -> Option<&'static str> { 62 | let class = match value { 63 | 0 => "QUERY", 64 | 1 => "IQUERY", 65 | 2 => "STATUS", 66 | 4 => "NOTIFY", 67 | 5 => "UPDATE", 68 | _ => return None, 69 | }; 70 | 71 | Some(class) 72 | } 73 | 74 | pub fn rr_rcode(value: usize) -> Option<&'static str> { 75 | let rcode = match value { 76 | 0 => "NOERROR", 77 | 1 => "FORMERR", 78 | 2 => "SERVFAIL", 79 | 3 => "NXDOMAIN", 80 | 4 => "NOTIMPL", 81 | 5 => "REFUSED", 82 | 6 => "YXDOMAIN", 83 | 7 => "YXRRSET", 84 | 8 => "NXRRSET", 85 | 9 => "NOTAUTH", 86 | 10 => "NOTZONE", 87 | _ => return None, 88 | }; 89 | 90 | Some(rcode) 91 | } 92 | -------------------------------------------------------------------------------- /src/sources/memory/wrappers.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | //use std::time::Duration; 3 | 4 | use super::ffi; 5 | use super::shm::Segment; 6 | 7 | use crate::statistics::Statistics; 8 | 9 | pub struct SharedMemory { 10 | server: Segment, 11 | threads: Segment, 12 | } 13 | 14 | impl SharedMemory { 15 | pub fn get(key: libc::key_t) -> io::Result { 16 | log::trace!("Acquiring shared memory region access for key {}", key); 17 | let server = Segment::::get(key)?; 18 | // TODO: `key + 1` might overflow 19 | let threads = Segment::::get(key + 1)?; 20 | log::debug!( 21 | "Successfully acquired an access to the unbound shared memory region with key {}", 22 | key 23 | ); 24 | 25 | Ok(SharedMemory { server, threads }) 26 | } 27 | 28 | pub fn server(&self) -> &ffi::ShmStatInfo { 29 | &self.server 30 | } 31 | 32 | pub fn total(&self) -> &ffi::StatsInfo { 33 | &self.threads 34 | } 35 | 36 | pub fn threads(&self) -> &[ffi::StatsInfo] { 37 | if self.server.num_threads > 0 { 38 | let threads = unsafe { self.threads.as_slice(self.server.num_threads as usize + 1) }; 39 | &threads[1..] 40 | } else { 41 | &[] 42 | } 43 | } 44 | } 45 | 46 | impl From for Statistics { 47 | fn from(_shm: SharedMemory) -> Statistics { 48 | // let server = shm.server(); 49 | // let total = shm.total(); 50 | // let threads = shm.threads(); 51 | 52 | unimplemented!() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/sources/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use crate::Statistics; 4 | 5 | mod control; 6 | #[cfg(unix)] 7 | mod memory; 8 | 9 | #[cfg(unix)] 10 | pub use self::control::UdsTransport; 11 | pub use self::control::{RemoteControlSource, TextTransport, TlsTransport}; 12 | #[cfg(unix)] 13 | pub use self::memory::SharedMemorySource; 14 | 15 | /// Source to fetch `unbound` statistics from. 16 | /// 17 | /// It could be shared memory region or TLS socket, for example. 18 | #[async_trait::async_trait] 19 | pub trait Source { 20 | /// Check if connection to `unbound` can be established. 21 | async fn healthcheck(&self) -> io::Result<()>; 22 | 23 | /// Attempt to fetch the `unbound` statistics from this source. 24 | async fn observe(&self) -> io::Result; 25 | } 26 | -------------------------------------------------------------------------------- /src/statistics/histogram.rs: -------------------------------------------------------------------------------- 1 | use std::num; 2 | use std::time::Duration; 3 | 4 | #[derive(Debug)] 5 | struct InnerBucket { 6 | le: Duration, 7 | count: u64, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct Histogram { 12 | buckets: Vec, 13 | average: f64, 14 | } 15 | 16 | impl Histogram { 17 | pub fn new(total_recursion_time_avg: f64) -> Self { 18 | Self { 19 | buckets: Vec::with_capacity(40), 20 | average: total_recursion_time_avg, 21 | } 22 | } 23 | 24 | pub fn average_mut(&mut self) -> &mut f64 { 25 | &mut self.average 26 | } 27 | 28 | pub fn push(&mut self, le: Duration, count: u64) { 29 | self.buckets.push(InnerBucket { le, count }) 30 | } 31 | 32 | pub fn sum(&self) -> f64 { 33 | self.average * self.count() as f64 34 | } 35 | 36 | pub fn count(&self) -> u64 { 37 | self.buckets 38 | .iter() 39 | .map(|bucket| num::Wrapping(bucket.count)) 40 | .sum::>() 41 | .0 42 | } 43 | 44 | pub fn buckets(&mut self) -> Buckets<'_> { 45 | Buckets::new(&mut self.buckets) 46 | } 47 | } 48 | 49 | impl Default for Histogram { 50 | fn default() -> Self { 51 | Self { 52 | buckets: Vec::with_capacity(40), 53 | average: 0.0, 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct Buckets<'h> { 60 | buckets: &'h mut [InnerBucket], 61 | total: num::Wrapping, 62 | current: usize, 63 | inf_yielded: bool, 64 | } 65 | 66 | impl<'h> Buckets<'h> { 67 | fn new(buckets: &'h mut [InnerBucket]) -> Buckets<'h> { 68 | buckets.sort_unstable_by_key(|bucket| bucket.le); 69 | 70 | Buckets { 71 | buckets, 72 | total: num::Wrapping(0u64), 73 | current: 0, 74 | inf_yielded: false, 75 | } 76 | } 77 | } 78 | 79 | impl<'h> Iterator for Buckets<'h> { 80 | type Item = Bucket; 81 | 82 | fn next(&mut self) -> Option { 83 | if self.current < self.buckets.len() { 84 | let bucket = &self.buckets[self.current]; 85 | self.current += 1; 86 | self.total += num::Wrapping(bucket.count); 87 | 88 | Some(Bucket::Le(bucket.le, self.total.0)) 89 | } else if !self.inf_yielded { 90 | self.inf_yielded = true; 91 | 92 | Some(Bucket::Inf(self.total.0)) 93 | } else { 94 | None 95 | } 96 | } 97 | } 98 | 99 | #[derive(Debug, PartialEq, Eq)] 100 | pub enum Bucket { 101 | Le(Duration, u64), 102 | Inf(u64), 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use std::time::Duration; 108 | 109 | use super::{Bucket, Histogram}; 110 | 111 | #[test] 112 | #[allow(clippy::cognitive_complexity)] 113 | fn test_histogram() { 114 | let source = vec![ 115 | // Roughly in the same format what `unbound-control stats_noreset` command outputs 116 | (Duration::from_secs_f64(000_000.000_001), 0), 117 | (Duration::from_secs_f64(000_000.000_002), 0), 118 | (Duration::from_secs_f64(000_000.000_004), 0), 119 | (Duration::from_secs_f64(000_000.000_008), 0), 120 | (Duration::from_secs_f64(000_000.000_016), 0), 121 | (Duration::from_secs_f64(000_000.000_032), 0), 122 | (Duration::from_secs_f64(000_000.000_064), 0), 123 | (Duration::from_secs_f64(000_000.000_128), 0), 124 | (Duration::from_secs_f64(000_000.000_256), 0), 125 | (Duration::from_secs_f64(000_000.000_512), 0), 126 | (Duration::from_secs_f64(000_000.001_024), 0), 127 | (Duration::from_secs_f64(000_000.002_048), 0), 128 | (Duration::from_secs_f64(000_000.004_096), 0), 129 | (Duration::from_secs_f64(000_000.008_192), 0), 130 | (Duration::from_secs_f64(000_000.016_384), 0), 131 | (Duration::from_secs_f64(000_000.032_768), 0), 132 | (Duration::from_secs_f64(000_000.065_536), 2), 133 | (Duration::from_secs_f64(000_000.131_072), 1), 134 | (Duration::from_secs_f64(000_000.262_144), 1), 135 | (Duration::from_secs_f64(000_000.524_288), 2), 136 | (Duration::from_secs_f64(000_001.000_000), 1), 137 | (Duration::from_secs_f64(000_002.000_000), 0), 138 | (Duration::from_secs_f64(000_004.000_000), 0), 139 | (Duration::from_secs_f64(000_008.000_000), 0), 140 | (Duration::from_secs_f64(000_016.000_000), 0), 141 | (Duration::from_secs_f64(000_032.000_000), 0), 142 | (Duration::from_secs_f64(000_064.000_000), 0), 143 | (Duration::from_secs_f64(000_128.000_000), 0), 144 | (Duration::from_secs_f64(000_256.000_000), 0), 145 | (Duration::from_secs_f64(000_512.000_000), 0), 146 | (Duration::from_secs_f64(001_024.000_000), 0), 147 | (Duration::from_secs_f64(002_048.000_000), 0), 148 | (Duration::from_secs_f64(004_096.000_000), 0), 149 | (Duration::from_secs_f64(008_192.000_000), 0), 150 | (Duration::from_secs_f64(016_384.000_000), 0), 151 | (Duration::from_secs_f64(032_768.000_000), 0), 152 | (Duration::from_secs_f64(065_536.000_000), 0), 153 | (Duration::from_secs_f64(131_072.000_000), 0), 154 | (Duration::from_secs_f64(262_144.000_000), 0), 155 | (Duration::from_secs_f64(524_288.000_000), 0), 156 | ]; 157 | assert_eq!(source.len(), 40); 158 | 159 | let mut h = Histogram::new(0.280_601); 160 | for (le, value) in source.into_iter() { 161 | h.push(le, value); 162 | } 163 | 164 | approx::assert_relative_eq!(h.sum(), 1.964_207); 165 | assert_eq!(h.count(), 7); 166 | 167 | let mut buckets = h.buckets(); 168 | 169 | assert_eq!( 170 | buckets.next(), 171 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_001), 0)) 172 | ); 173 | assert_eq!( 174 | buckets.next(), 175 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_002), 0)) 176 | ); 177 | assert_eq!( 178 | buckets.next(), 179 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_004), 0)) 180 | ); 181 | assert_eq!( 182 | buckets.next(), 183 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_008), 0)) 184 | ); 185 | assert_eq!( 186 | buckets.next(), 187 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_016), 0)) 188 | ); 189 | assert_eq!( 190 | buckets.next(), 191 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_032), 0)) 192 | ); 193 | assert_eq!( 194 | buckets.next(), 195 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_064), 0)) 196 | ); 197 | assert_eq!( 198 | buckets.next(), 199 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_128), 0)) 200 | ); 201 | assert_eq!( 202 | buckets.next(), 203 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_256), 0)) 204 | ); 205 | assert_eq!( 206 | buckets.next(), 207 | Some(Bucket::Le(Duration::from_secs_f64(000_000.000_512), 0)) 208 | ); 209 | assert_eq!( 210 | buckets.next(), 211 | Some(Bucket::Le(Duration::from_secs_f64(000_000.001_024), 0)) 212 | ); 213 | assert_eq!( 214 | buckets.next(), 215 | Some(Bucket::Le(Duration::from_secs_f64(000_000.002_048), 0)) 216 | ); 217 | assert_eq!( 218 | buckets.next(), 219 | Some(Bucket::Le(Duration::from_secs_f64(000_000.004_096), 0)) 220 | ); 221 | assert_eq!( 222 | buckets.next(), 223 | Some(Bucket::Le(Duration::from_secs_f64(000_000.008_192), 0)) 224 | ); 225 | assert_eq!( 226 | buckets.next(), 227 | Some(Bucket::Le(Duration::from_secs_f64(000_000.016_384), 0)) 228 | ); 229 | assert_eq!( 230 | buckets.next(), 231 | Some(Bucket::Le(Duration::from_secs_f64(000_000.032_768), 0)) 232 | ); 233 | assert_eq!( 234 | buckets.next(), 235 | Some(Bucket::Le(Duration::from_secs_f64(000_000.065_536), 2)) 236 | ); 237 | assert_eq!( 238 | buckets.next(), 239 | Some(Bucket::Le(Duration::from_secs_f64(000_000.131_072), 3)) 240 | ); 241 | assert_eq!( 242 | buckets.next(), 243 | Some(Bucket::Le(Duration::from_secs_f64(000_000.262_144), 4)) 244 | ); 245 | assert_eq!( 246 | buckets.next(), 247 | Some(Bucket::Le(Duration::from_secs_f64(000_000.524_288), 6)) 248 | ); 249 | assert_eq!( 250 | buckets.next(), 251 | Some(Bucket::Le(Duration::from_secs_f64(000_001.000_000), 7)) 252 | ); 253 | assert_eq!( 254 | buckets.next(), 255 | Some(Bucket::Le(Duration::from_secs_f64(000_002.000_000), 7)) 256 | ); 257 | assert_eq!( 258 | buckets.next(), 259 | Some(Bucket::Le(Duration::from_secs_f64(000_004.000_000), 7)) 260 | ); 261 | assert_eq!( 262 | buckets.next(), 263 | Some(Bucket::Le(Duration::from_secs_f64(000_008.000_000), 7)) 264 | ); 265 | assert_eq!( 266 | buckets.next(), 267 | Some(Bucket::Le(Duration::from_secs_f64(000_016.000_000), 7)) 268 | ); 269 | assert_eq!( 270 | buckets.next(), 271 | Some(Bucket::Le(Duration::from_secs_f64(000_032.000_000), 7)) 272 | ); 273 | assert_eq!( 274 | buckets.next(), 275 | Some(Bucket::Le(Duration::from_secs_f64(000_064.000_000), 7)) 276 | ); 277 | assert_eq!( 278 | buckets.next(), 279 | Some(Bucket::Le(Duration::from_secs_f64(000_128.000_000), 7)) 280 | ); 281 | assert_eq!( 282 | buckets.next(), 283 | Some(Bucket::Le(Duration::from_secs_f64(000_256.000_000), 7)) 284 | ); 285 | assert_eq!( 286 | buckets.next(), 287 | Some(Bucket::Le(Duration::from_secs_f64(000_512.000_000), 7)) 288 | ); 289 | assert_eq!( 290 | buckets.next(), 291 | Some(Bucket::Le(Duration::from_secs_f64(001_024.000_000), 7)) 292 | ); 293 | assert_eq!( 294 | buckets.next(), 295 | Some(Bucket::Le(Duration::from_secs_f64(002_048.000_000), 7)) 296 | ); 297 | assert_eq!( 298 | buckets.next(), 299 | Some(Bucket::Le(Duration::from_secs_f64(004_096.000_000), 7)) 300 | ); 301 | assert_eq!( 302 | buckets.next(), 303 | Some(Bucket::Le(Duration::from_secs_f64(008_192.000_000), 7)) 304 | ); 305 | assert_eq!( 306 | buckets.next(), 307 | Some(Bucket::Le(Duration::from_secs_f64(016_384.000_000), 7)) 308 | ); 309 | assert_eq!( 310 | buckets.next(), 311 | Some(Bucket::Le(Duration::from_secs_f64(032_768.000_000), 7)) 312 | ); 313 | assert_eq!( 314 | buckets.next(), 315 | Some(Bucket::Le(Duration::from_secs_f64(065_536.000_000), 7)) 316 | ); 317 | assert_eq!( 318 | buckets.next(), 319 | Some(Bucket::Le(Duration::from_secs_f64(131_072.000_000), 7)) 320 | ); 321 | assert_eq!( 322 | buckets.next(), 323 | Some(Bucket::Le(Duration::from_secs_f64(262_144.000_000), 7)) 324 | ); 325 | assert_eq!( 326 | buckets.next(), 327 | Some(Bucket::Le(Duration::from_secs_f64(524_288.000_000), 7)) 328 | ); 329 | assert_eq!(buckets.next(), Some(Bucket::Inf(7))); 330 | assert_eq!(buckets.next(), None); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/statistics/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::str; 3 | use std::time::Duration; 4 | 5 | use domain::base::iana::{Class, Opcode, Rcode, Rtype}; 6 | 7 | mod histogram; 8 | mod parser; 9 | 10 | pub use self::histogram::{Bucket, Histogram}; 11 | pub use self::parser::ParseError; 12 | 13 | /// Statistics snapshot received from some data source. 14 | /// 15 | /// It is decoupled from any data layout or format exposed by `unbound` 16 | /// and mostly exists only to make sure that all keys are provided by all the data sources. 17 | #[derive(Debug, Default)] 18 | pub struct Statistics { 19 | pub total: Thread, 20 | pub threads: Vec, 21 | pub time: Time, 22 | pub cache: Cache, 23 | pub modules: Modules, 24 | pub cache_count: CacheCounter, 25 | pub http: Http, 26 | pub flags: Flags, 27 | pub query_opcodes: HashMap, 28 | pub query_types: HashMap, 29 | // All other `Rtype` entries higher than `UB_STATS_QTYPE_NUM` (declared in `unbound.h`) 30 | // are summed together into one metric value. 31 | // As they are not representing any specific `Rtype`, storing them separately in here too. 32 | pub query_types_other: u64, 33 | pub query_classes: HashMap, 34 | // See `query_types_other` comment for motivation to have this separate field. 35 | pub query_classes_other: u64, 36 | pub answer_rcodes: HashMap, 37 | pub query_aggressive: HashMap, 38 | pub histogram: Histogram, 39 | pub mem_streamwait: u64, 40 | pub num_query_tcp: u64, 41 | pub num_query_tcp_out: u64, 42 | pub num_query_tls: u64, 43 | pub num_query_tls_resume: u64, 44 | pub num_query_ipv6: u64, 45 | pub num_query_edns_present: u64, 46 | pub num_query_edns_do: u64, 47 | pub num_query_rate_limited: u64, 48 | pub num_query_https: u64, 49 | pub num_answer_secure: u64, 50 | pub num_answer_bogus: u64, 51 | pub num_rrset_bogus: u64, 52 | pub num_unwanted_queries: u64, 53 | pub num_unwanted_replies: u64, 54 | pub num_query_dnscrypt_shared_secret_cache_miss: u64, 55 | pub num_query_dnscrypt_replay: u64, 56 | pub num_query_authzone_up: u64, 57 | pub num_query_authzone_down: u64, 58 | pub num_query_subnet: u64, 59 | pub num_query_subnet_cache: u64, 60 | } 61 | 62 | impl str::FromStr for Statistics { 63 | type Err = ParseError; 64 | 65 | fn from_str(s: &str) -> Result { 66 | let parser = self::parser::Parser::new(); 67 | parser.parse(s) 68 | } 69 | } 70 | 71 | /// Thread related data. 72 | #[derive(Debug, Default)] 73 | pub struct Thread { 74 | // Num 75 | pub num_queries: u64, 76 | pub num_queries_ip_ratelimited: u64, 77 | pub num_cache_hits: u64, 78 | pub num_cache_miss: u64, 79 | pub num_prefetch: u64, 80 | pub num_zero_ttl: u64, 81 | pub num_recursive_replies: u64, 82 | pub num_dnscrypt_crypted: u64, 83 | pub num_dnscrypt_cert: u64, 84 | pub num_dnscrypt_cleartext: u64, 85 | pub num_dnscrypt_malformed: u64, 86 | pub requestlist_avg: f64, 87 | pub requestlist_max: u64, 88 | pub requestlist_overwritten: u64, 89 | pub requestlist_exceeded: u64, 90 | pub requestlist_current_all: u64, 91 | pub requestlist_current_user: u64, 92 | pub recursion_time_avg: f64, 93 | pub recursion_time_median: f64, 94 | pub tcp_usage: u64, 95 | pub answer_rcode: HashMap, 96 | } 97 | 98 | #[derive(Debug, Default)] 99 | pub struct Time { 100 | pub now: Duration, 101 | pub up: Duration, 102 | pub elapsed: Duration, 103 | } 104 | 105 | #[derive(Debug, Default)] 106 | pub struct Cache { 107 | pub rrset: u64, 108 | pub message: u64, 109 | pub dnscrypt_shared_secret: u64, 110 | pub dnscrypt_nonce: u64, 111 | } 112 | 113 | #[derive(Debug, Default)] 114 | pub struct Modules { 115 | pub iterator: u64, 116 | pub validator: u64, 117 | pub respip: u64, 118 | pub subnet: u64, 119 | } 120 | 121 | #[derive(Debug, Default)] 122 | pub struct CacheCounter { 123 | pub message: u64, 124 | pub rrset: u64, 125 | pub infra: u64, 126 | pub key: u64, 127 | pub dnscrypt_shared_secret: u64, 128 | pub dnscrypt_nonce: u64, 129 | } 130 | 131 | #[derive(Debug, Default)] 132 | pub struct Flags { 133 | pub qr: u64, 134 | pub aa: u64, 135 | pub tc: u64, 136 | pub rd: u64, 137 | pub ra: u64, 138 | pub z: u64, 139 | pub ad: u64, 140 | pub cd: u64, 141 | } 142 | 143 | #[derive(Debug, Default)] 144 | pub struct Http { 145 | pub query_buffer: u64, 146 | pub response_buffer: u64, 147 | } 148 | -------------------------------------------------------------------------------- /src/statistics/parser/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | use std::num; 4 | 5 | #[derive(Debug)] 6 | pub enum ParseError { 7 | UnknownKey { 8 | key: String, 9 | }, 10 | MissingKey, 11 | MissingValue { 12 | key: String, 13 | }, 14 | ParseInt(num::ParseIntError), 15 | ParseFloat(num::ParseFloatError), 16 | ParseStr(String), 17 | 18 | /// Generic error kind if none of other variants can suit to the case. 19 | InvalidFormat, 20 | } 21 | 22 | impl fmt::Display for ParseError { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | match self { 25 | ParseError::UnknownKey { key } => f.write_fmt(format_args!("Unknown key {}", key)), 26 | ParseError::MissingKey => f.write_str("Text line is missing a key definition"), 27 | ParseError::MissingValue { key } => f.write_fmt(format_args!("Missing value for key {}", key)), 28 | ParseError::ParseInt(e) => fmt::Display::fmt(e, f), 29 | ParseError::ParseFloat(e) => fmt::Display::fmt(e, f), 30 | ParseError::ParseStr(value) => f.write_fmt(format_args!("Unable to parse '{}' value", value)), 31 | ParseError::InvalidFormat => f.write_str("Invalid data format"), 32 | } 33 | } 34 | } 35 | 36 | impl error::Error for ParseError { 37 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 38 | match self { 39 | ParseError::ParseInt(e) => Some(e), 40 | ParseError::ParseFloat(e) => Some(e), 41 | _ => None, 42 | } 43 | } 44 | } 45 | 46 | impl From for ParseError { 47 | fn from(e: num::ParseFloatError) -> Self { 48 | ParseError::ParseFloat(e) 49 | } 50 | } 51 | 52 | impl From for ParseError { 53 | fn from(e: num::ParseIntError) -> Self { 54 | ParseError::ParseInt(e) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/statistics/parser/mod.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::time::Duration; 3 | use std::u64; 4 | 5 | mod errors; 6 | mod types; 7 | 8 | pub use self::errors::ParseError; 9 | use self::types::{parse_class, parse_rcode, parse_rtype, DurationExt, Field}; 10 | use super::{Opcode, Statistics, Thread}; 11 | use crate::statistics::Histogram; 12 | 13 | /// Parser for [`Statistics`] from the string representation. 14 | /// 15 | /// This representation can be obtained from the Unix or TLS socket. 16 | /// Alternatively, it can be received from `unbound-control stats_noreset` 17 | /// (or other `stats_*` commands), but these data sources will not be supported. 18 | #[derive(Debug)] 19 | pub struct Parser { 20 | stats: Statistics, 21 | } 22 | 23 | impl Parser { 24 | pub fn new() -> Parser { 25 | Parser::default() 26 | } 27 | 28 | pub fn parse(mut self, s: &str) -> Result { 29 | for line in s.lines() { 30 | let line = line.trim(); 31 | if line.is_empty() { 32 | continue; 33 | } 34 | 35 | match self.feed_line(line) { 36 | Ok(..) => {} 37 | Err(ParseError::UnknownKey { .. }) => { 38 | log::warn!("Unable to parse '{}', unknown key", line); 39 | } 40 | Err(e) => return Err(e), 41 | } 42 | } 43 | 44 | *self.stats.histogram.average_mut() = self.stats.total.recursion_time_avg; 45 | 46 | self.finish() 47 | } 48 | 49 | // For now assuming that all data was provided correctly 50 | pub fn finish(self) -> Result { 51 | Ok(self.stats) 52 | } 53 | 54 | pub fn feed_line(&mut self, line: &str) -> Result<(), ParseError> { 55 | let mut parts = line.splitn(2, '='); 56 | let key = parts.next().ok_or(ParseError::MissingKey)?; 57 | let value = parts 58 | .next() 59 | .ok_or_else(|| ParseError::MissingValue { key: key.into() })?; 60 | let mut key_parts = key.splitn(2, '.'); 61 | let key_prefix = key_parts.next().ok_or(ParseError::InvalidFormat)?; 62 | let key_postfix = key_parts.next().ok_or(ParseError::InvalidFormat)?; 63 | 64 | match key_prefix { 65 | "total" => Self::thread(&mut self.stats.total, key_postfix, value)?, 66 | prefix if prefix.starts_with("thread") => { 67 | // TODO: Unnecessary allocation 68 | let thread_id = prefix 69 | .chars() 70 | .skip("thread".len()) 71 | .collect::() 72 | .parse::()?; 73 | 74 | if cfg!(fuzzing) && thread_id > 255 { 75 | return Err(ParseError::InvalidFormat); 76 | } 77 | 78 | self.stats.threads.resize_with(thread_id + 1, Default::default); 79 | let mut thread = self 80 | .stats 81 | .threads 82 | .get_mut(thread_id) 83 | .expect("Can't happen, resize_with will handle that"); 84 | 85 | Self::thread(&mut thread, key_postfix, value)? 86 | } 87 | "histogram" => Self::histogram(&mut self.stats.histogram, key_postfix, value)?, 88 | _ => Self::other(&mut self.stats, key, value)?, 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | fn thread(thread: &mut Thread, key: &str, value: &str) -> Result<(), ParseError> { 95 | match key { 96 | "num.queries" => thread.num_queries.parse(value), 97 | "num.queries_ip_ratelimited" => thread.num_queries_ip_ratelimited.parse(value), 98 | "num.cachehits" => thread.num_cache_hits.parse(value), 99 | "num.cachemiss" => thread.num_cache_miss.parse(value), 100 | "num.prefetch" => thread.num_prefetch.parse(value), 101 | 102 | // Metric name before unbound version 1.10.1 103 | "num.zero_ttl" => thread.num_zero_ttl.parse(value), 104 | // Metric name after unbound version 1.10.1 105 | // see https://github.com/NLnetLabs/unbound/commit/f7fe95ad7bae690781f9b78ca252a44fc072ca33 106 | "num.expired" => thread.num_zero_ttl.parse(value), 107 | 108 | "num.recursivereplies" => thread.num_recursive_replies.parse(value), 109 | "num.dnscrypt.crypted" => thread.num_dnscrypt_crypted.parse(value), 110 | "num.dnscrypt.cert" => thread.num_dnscrypt_cert.parse(value), 111 | "num.dnscrypt.cleartext" => thread.num_dnscrypt_cleartext.parse(value), 112 | "num.dnscrypt.malformed" => thread.num_dnscrypt_malformed.parse(value), 113 | "requestlist.avg" => thread.requestlist_avg.parse(value), 114 | "requestlist.max" => thread.requestlist_max.parse(value), 115 | "requestlist.overwritten" => thread.requestlist_overwritten.parse(value), 116 | "requestlist.exceeded" => thread.requestlist_exceeded.parse(value), 117 | "requestlist.current.all" => thread.requestlist_current_all.parse(value), 118 | "requestlist.current.user" => thread.requestlist_current_user.parse(value), 119 | "recursion.time.avg" => thread.recursion_time_avg.parse(value), 120 | "recursion.time.median" => thread.recursion_time_median.parse(value), 121 | "tcpusage" => thread.tcp_usage.parse(value), 122 | _ => Err(ParseError::UnknownKey { key: key.into() }), 123 | } 124 | } 125 | 126 | fn other(stats: &mut Statistics, key: &str, value: &str) -> Result<(), ParseError> { 127 | match key { 128 | "time.now" => stats.time.now.parse(value), 129 | "time.up" => stats.time.up.parse(value), 130 | "time.elapsed" => stats.time.elapsed.parse(value), 131 | "mem.cache.rrset" => stats.cache.rrset.parse(value), 132 | "mem.cache.message" => stats.cache.message.parse(value), 133 | "mem.mod.iterator" => stats.modules.iterator.parse(value), 134 | "mem.mod.validator" => stats.modules.validator.parse(value), 135 | "mem.mod.respip" => stats.modules.respip.parse(value), 136 | "mem.mod.subnet" => stats.modules.subnet.parse(value), 137 | "mem.cache.dnscrypt_shared_secret" => stats.cache.dnscrypt_shared_secret.parse(value), 138 | "mem.cache.dnscrypt_nonce" => stats.cache.dnscrypt_nonce.parse(value), 139 | "mem.streamwait" => stats.mem_streamwait.parse(value), 140 | "mem.http.query_buffer" => stats.http.query_buffer.parse(value), 141 | "mem.http.response_buffer" => stats.http.response_buffer.parse(value), 142 | "num.query.type.other" => stats.query_types_other.parse(value), 143 | key if key.starts_with("num.query.type.") => { 144 | let mut parts = key.rsplitn(2, '.'); 145 | let raw_type = parts.next().ok_or(ParseError::InvalidFormat)?; 146 | // TODO: Unknown rtypes are ignored for now, see #101 147 | if let Ok(type_) = parse_rtype(raw_type) { 148 | let value = value.parse::()?; 149 | let _ = stats.query_types.insert(type_, value); 150 | } 151 | 152 | Ok(()) 153 | } 154 | "num.query.class.other" => stats.query_classes_other.parse(value), 155 | key if key.starts_with("num.query.class.") => { 156 | let mut parts = key.rsplitn(2, '.'); 157 | let raw_class = parts.next().ok_or(ParseError::InvalidFormat)?; 158 | // TODO: Unknown classes are ignored for now, see #101 159 | if let Ok(class) = parse_class(raw_class) { 160 | let value = value.parse::()?; 161 | let _ = stats.query_classes.insert(class, value); 162 | } 163 | 164 | Ok(()) 165 | } 166 | key if key.starts_with("num.query.opcode.") => { 167 | let mut parts = key.rsplitn(2, '.'); 168 | let raw_code = parts.next().ok_or(ParseError::InvalidFormat)?; 169 | // TODO: Unknown opcodes are ignored for now, see #101 170 | let code = Opcode::from_str(raw_code).map_err(|_| ParseError::ParseStr(raw_code.into())); 171 | if let Ok(code) = code { 172 | let value = value.parse::()?; 173 | let _ = stats.query_opcodes.insert(code, value); 174 | } 175 | Ok(()) 176 | } 177 | "num.query.tcp" => stats.num_query_tcp.parse(value), 178 | "num.query.tcpout" => stats.num_query_tcp_out.parse(value), 179 | "num.query.tls" => stats.num_query_tls.parse(value), 180 | "num.query.tls.resume" => stats.num_query_tls_resume.parse(value), 181 | "num.query.ipv6" => stats.num_query_ipv6.parse(value), 182 | "num.query.https" => stats.num_query_https.parse(value), 183 | "num.query.flags.QR" => stats.flags.qr.parse(value), 184 | "num.query.flags.AA" => stats.flags.aa.parse(value), 185 | "num.query.flags.TC" => stats.flags.tc.parse(value), 186 | "num.query.flags.RD" => stats.flags.rd.parse(value), 187 | "num.query.flags.RA" => stats.flags.ra.parse(value), 188 | "num.query.flags.Z" => stats.flags.z.parse(value), 189 | "num.query.flags.AD" => stats.flags.ad.parse(value), 190 | "num.query.flags.CD" => stats.flags.cd.parse(value), 191 | "num.query.edns.present" => stats.num_query_edns_present.parse(value), 192 | "num.query.edns.DO" => stats.num_query_edns_do.parse(value), 193 | // `rcode.nodata` is ignored, same to `kumina/unbound_exporter` 194 | "num.answer.rcode.nodata" => Ok(()), 195 | key if key.starts_with("num.answer.rcode.") => { 196 | let mut parts = key.rsplitn(2, '.'); 197 | let raw_code = parts.next().ok_or(ParseError::InvalidFormat)?; 198 | let code = parse_rcode(raw_code)?; 199 | let value = value.parse::()?; 200 | let _ = stats.answer_rcodes.insert(code, value); 201 | Ok(()) 202 | } 203 | "num.query.ratelimited" => stats.num_query_rate_limited.parse(value), 204 | "num.answer.secure" => stats.num_answer_secure.parse(value), 205 | "num.answer.bogus" => stats.num_answer_bogus.parse(value), 206 | "num.rrset.bogus" => stats.num_rrset_bogus.parse(value), 207 | key if key.starts_with("num.query.aggressive.") => { 208 | let mut parts = key.rsplitn(2, '.'); 209 | let raw_code = parts.next().ok_or(ParseError::InvalidFormat)?; 210 | let code = parse_rcode(raw_code)?; 211 | let value = value.parse::()?; 212 | let _ = stats.query_aggressive.insert(code, value); 213 | Ok(()) 214 | } 215 | "unwanted.queries" => stats.num_unwanted_queries.parse(value), 216 | "unwanted.replies" => stats.num_unwanted_replies.parse(value), 217 | "msg.cache.count" => stats.cache_count.message.parse(value), 218 | "rrset.cache.count" => stats.cache_count.rrset.parse(value), 219 | "infra.cache.count" => stats.cache_count.infra.parse(value), 220 | "key.cache.count" => stats.cache_count.key.parse(value), 221 | "dnscrypt_shared_secret.cache.count" => stats.cache_count.dnscrypt_shared_secret.parse(value), 222 | "dnscrypt_nonce.cache.count" => stats.cache_count.dnscrypt_nonce.parse(value), 223 | "num.query.dnscrypt.shared_secret.cachemiss" => { 224 | stats.num_query_dnscrypt_shared_secret_cache_miss.parse(value) 225 | } 226 | "num.query.dnscrypt.replay" => stats.num_query_dnscrypt_replay.parse(value), 227 | "num.query.authzone.up" => stats.num_query_authzone_up.parse(value), 228 | "num.query.authzone.down" => stats.num_query_authzone_down.parse(value), 229 | "num.query.subnet" => stats.num_query_subnet.parse(value), 230 | "num.query.subnet_cache" => stats.num_query_subnet_cache.parse(value), 231 | _ => Err(ParseError::UnknownKey { key: key.into() }), 232 | } 233 | } 234 | 235 | fn histogram(hist: &mut Histogram, key: &str, value: &str) -> Result<(), ParseError> { 236 | let mut parts = key.splitn(4, '.').skip(3); 237 | let time = parts.next().ok_or(ParseError::InvalidFormat)?.parse::()?; 238 | 239 | let duration = Duration::checked_from_secs_f64(time).ok_or(ParseError::InvalidFormat)?; 240 | let value = value.parse()?; 241 | 242 | hist.push(duration, value); 243 | 244 | Ok(()) 245 | } 246 | } 247 | 248 | impl Default for Parser { 249 | fn default() -> Self { 250 | Parser { 251 | stats: Statistics::default(), 252 | } 253 | } 254 | } 255 | 256 | #[cfg(test)] 257 | mod tests; 258 | -------------------------------------------------------------------------------- /src/statistics/parser/tests.rs: -------------------------------------------------------------------------------- 1 | use claim::{assert_ok, assert_some_eq}; 2 | 3 | use super::Parser; 4 | use crate::statistics::{Class, Rcode, Rtype}; 5 | 6 | static STATS: &str = include_str!("../../../assets/test_text_stats.txt"); 7 | static STATS_1_13_2: &str = include_str!("../../../assets/test_text_stats_1_13_2.txt"); 8 | 9 | #[test] 10 | #[allow(clippy::cognitive_complexity)] 11 | fn test_parser() { 12 | let parser = Parser::new(); 13 | let result = parser.parse(STATS); 14 | let stats = result.unwrap(); 15 | 16 | // TODO: approx eq for a whole value 17 | assert_eq!(stats.time.now.as_secs(), 1_580_162_982); 18 | assert_eq!(stats.time.up.as_secs(), 32_586); 19 | assert_eq!(stats.time.elapsed.as_secs(), 32_586); 20 | 21 | assert_eq!(stats.cache.rrset, 443_440); 22 | assert_eq!(stats.cache.message, 312_898); 23 | assert_eq!(stats.modules.iterator, 16_588); 24 | assert_eq!(stats.modules.validator, 140_392); 25 | assert_eq!(stats.modules.respip, 0); 26 | assert_eq!(stats.modules.subnet, 0); 27 | assert_eq!(stats.cache.dnscrypt_shared_secret, 0); 28 | assert_eq!(stats.cache.dnscrypt_nonce, 0); 29 | assert_eq!(stats.mem_streamwait, 0); 30 | 31 | assert_some_eq!(stats.query_types.get(&Rtype::from_int(0)), &21838); 32 | assert_some_eq!(stats.query_types.get(&Rtype::A), &4576639648); 33 | assert_some_eq!(stats.query_types.get(&Rtype::Ns), &195714); 34 | assert_some_eq!(stats.query_types.get(&Rtype::Mf), &1); 35 | assert_some_eq!(stats.query_types.get(&Rtype::Cname), &223477); 36 | assert_some_eq!(stats.query_types.get(&Rtype::Soa), &252841); 37 | assert_some_eq!(stats.query_types.get(&Rtype::Mr), &2); 38 | assert_some_eq!(stats.query_types.get(&Rtype::Null), &238628); 39 | assert_some_eq!(stats.query_types.get(&Rtype::Wks), &230227); 40 | assert_some_eq!(stats.query_types.get(&Rtype::Ptr), &8446520); 41 | assert_some_eq!(stats.query_types.get(&Rtype::Hinfo), &231428); 42 | assert_some_eq!(stats.query_types.get(&Rtype::Mx), &34204); 43 | assert_some_eq!(stats.query_types.get(&Rtype::Txt), &510353); 44 | assert_some_eq!(stats.query_types.get(&Rtype::Aaaa), &151992709); 45 | assert_some_eq!(stats.query_types.get(&Rtype::Nxt), &3); 46 | assert_some_eq!(stats.query_types.get(&Rtype::Srv), &1883446); 47 | assert_some_eq!(stats.query_types.get(&Rtype::Naptr), &1578821); 48 | assert_some_eq!(stats.query_types.get(&Rtype::Ds), &215); 49 | assert_some_eq!(stats.query_types.get(&Rtype::Dnskey), &252); 50 | assert_some_eq!(stats.query_types.get(&Rtype::from_int(65)), &140375490); 51 | assert_some_eq!(stats.query_types.get(&Rtype::from_int(96)), &1); 52 | assert_some_eq!(stats.query_types.get(&Rtype::Any), &82558); 53 | 54 | assert_some_eq!(stats.query_classes.get(&Class::from_int(0)), &2); 55 | assert_some_eq!(stats.query_classes.get(&Class::In), &4882932525); 56 | assert_some_eq!(stats.query_classes.get(&Class::Ch), &1073); 57 | assert_some_eq!(stats.query_classes.get(&Class::Hs), &3); 58 | assert_some_eq!(stats.query_classes.get(&Class::from_int(5)), &1); 59 | 60 | assert_eq!(stats.num_query_tcp, 0); 61 | assert_eq!(stats.num_query_tcp_out, 2); 62 | assert_eq!(stats.num_query_tls, 0); 63 | assert_eq!(stats.num_query_tls_resume, 0); 64 | assert_eq!(stats.num_query_ipv6, 0); 65 | assert_eq!(stats.num_query_https, 10); 66 | 67 | assert_eq!(stats.flags.qr, 0); 68 | assert_eq!(stats.flags.aa, 0); 69 | assert_eq!(stats.flags.tc, 0); 70 | assert_eq!(stats.flags.rd, 1338); 71 | assert_eq!(stats.flags.ra, 0); 72 | assert_eq!(stats.flags.z, 0); 73 | assert_eq!(stats.flags.ad, 0); 74 | assert_eq!(stats.flags.cd, 0); 75 | 76 | assert_eq!(stats.num_query_edns_present, 0); 77 | assert_eq!(stats.num_query_edns_do, 0); 78 | 79 | assert_some_eq!(stats.answer_rcodes.get(&Rcode::NoError), &1315); 80 | assert_some_eq!(stats.answer_rcodes.get(&Rcode::FormErr), &0); 81 | assert_some_eq!(stats.answer_rcodes.get(&Rcode::ServFail), &0); 82 | assert_some_eq!(stats.answer_rcodes.get(&Rcode::NXDomain), &23); 83 | assert_some_eq!(stats.answer_rcodes.get(&Rcode::NotImp), &0); 84 | assert_some_eq!(stats.answer_rcodes.get(&Rcode::Refused), &0); 85 | 86 | assert_eq!(stats.http.query_buffer, 1024); 87 | assert_eq!(stats.http.response_buffer, 2048); 88 | 89 | assert_eq!(stats.threads.len(), 2); 90 | } 91 | 92 | #[test] 93 | fn test_parser_1_13_2_format() { 94 | let parser = Parser::new(); 95 | 96 | assert_ok!(parser.parse(STATS_1_13_2)); 97 | } 98 | -------------------------------------------------------------------------------- /src/statistics/parser/types.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::time::Duration; 3 | 4 | use domain::base::iana::{Class, Rcode}; 5 | 6 | use crate::statistics::ParseError; 7 | use domain::base::Rtype; 8 | 9 | pub(crate) trait Field: Sized { 10 | fn parse(&mut self, s: &str) -> Result<(), ParseError>; 11 | } 12 | 13 | impl Field for u64 { 14 | fn parse(&mut self, s: &str) -> Result<(), ParseError> { 15 | *self = s.parse().map_err(ParseError::from)?; 16 | Ok(()) 17 | } 18 | } 19 | 20 | impl Field for f64 { 21 | fn parse(&mut self, s: &str) -> Result<(), ParseError> { 22 | *self = s.parse().map_err(ParseError::from)?; 23 | Ok(()) 24 | } 25 | } 26 | 27 | impl Field for Duration { 28 | fn parse(&mut self, s: &str) -> Result<(), ParseError> { 29 | let value = s.parse::()?; 30 | 31 | match Duration::checked_from_secs_f64(value) { 32 | Some(duration) => { 33 | *self = duration; 34 | Ok(()) 35 | } 36 | None => Err(ParseError::InvalidFormat), 37 | } 38 | } 39 | } 40 | 41 | pub trait DurationExt { 42 | /// Following is a rip-off of the `Duration::from_secs_f64` method, 43 | /// since there is is no `Duration::checked_from_secs_f64` 44 | /// and we can't afford to panic 45 | #[inline] 46 | fn checked_from_secs_f64(secs: f64) -> Option { 47 | const NANOS_PER_SEC: u128 = 1_000_000_000; 48 | const MAX_NANOS_F64: f64 = ((u64::max_value() as u128 + 1) * NANOS_PER_SEC) as f64; 49 | 50 | let nanos = secs * (NANOS_PER_SEC as f64); 51 | if !nanos.is_finite() { 52 | return None; 53 | } 54 | if nanos >= MAX_NANOS_F64 { 55 | return None; 56 | } 57 | if nanos < 0.0 { 58 | return None; 59 | } 60 | let nanos = nanos as u128; 61 | Some(Duration::new( 62 | (nanos / NANOS_PER_SEC) as u64, 63 | (nanos % NANOS_PER_SEC) as u32, 64 | )) 65 | } 66 | } 67 | 68 | impl DurationExt for Duration {} 69 | 70 | /// `Rcode` enum from `domain` crate does not implement `FromStr`, 71 | /// but we need it to parse textual representation from `unbound`. 72 | /// 73 | /// Considering that text format is known, current implementation 74 | /// is pretty simple and even skips the case-sensitive checks. 75 | pub(crate) fn parse_rcode(s: &str) -> Result { 76 | match s { 77 | "NOERROR" => Ok(Rcode::NoError), 78 | "FORMERR" => Ok(Rcode::FormErr), 79 | "SERVFAIL" => Ok(Rcode::ServFail), 80 | "NXDOMAIN" => Ok(Rcode::NXDomain), 81 | "NOTIMPL" => Ok(Rcode::NotImp), 82 | "REFUSED" => Ok(Rcode::Refused), 83 | "YXDOMAIN" => Ok(Rcode::YXDomain), 84 | "YXRRSET" => Ok(Rcode::YXRRSet), 85 | "NXRRSET" => Ok(Rcode::NXRRSet), 86 | "NOTAUTH" => Ok(Rcode::NotAuth), 87 | "NOTZONE" => Ok(Rcode::NotZone), 88 | _ => Err(ParseError::ParseStr(s.to_owned())), 89 | } 90 | } 91 | 92 | /// `Class` enum from `domain` crate uses `"*"` as a text representation for `Class::Any` 93 | /// enum member. 94 | /// 95 | /// While this is obviously correct behavior, text representation of `unbound` statistics 96 | /// outputs `"ANY"` instead of `"*"`, which means we needs to handle this case separately. 97 | pub(crate) fn parse_class(s: &str) -> Result { 98 | match s { 99 | "ANY" => Ok(Class::Any), 100 | other => Class::from_str(other).map_err(|e| ParseError::ParseStr(format!("{}", e))), 101 | } 102 | } 103 | 104 | /// `Rtype` enum from `domain` crate have some differences with query types `unbound` returns, 105 | /// so we need to map them. 106 | pub(crate) fn parse_rtype(s: &str) -> Result { 107 | match s { 108 | "NSAP-PTR" => Ok(Rtype::Nsapptr), 109 | other => Rtype::from_str(other).map_err(|e| ParseError::ParseStr(format!("{}", e))), 110 | } 111 | } 112 | --------------------------------------------------------------------------------