├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── NOTICE
├── README.md
├── build.rs
├── dnstap.pb
└── dnstap.proto
└── src
├── bin
├── dnstap-dump
│ └── main.rs
├── dnstap-inject
│ └── main.rs
├── dnstap-replay
│ ├── dnstap_handler.rs
│ ├── frame_handler.rs
│ ├── http_handler.rs
│ ├── main.rs
│ ├── metrics.rs
│ └── monitor_handler.rs
└── fmt-dns-message
│ └── main.rs
├── framestreams_codec.rs
├── lib.rs
├── proxyv2.rs
└── util.rs
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Install build dependencies
20 | run: sudo apt-get -y install protobuf-compiler
21 | - name: Build
22 | run: cargo build --verbose
23 | - name: Run tests
24 | run: cargo test --verbose
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.21.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler"
16 | version = "1.0.2"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 |
20 | [[package]]
21 | name = "aho-corasick"
22 | version = "1.1.2"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
25 | dependencies = [
26 | "memchr",
27 | ]
28 |
29 | [[package]]
30 | name = "anstream"
31 | version = "0.6.7"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba"
34 | dependencies = [
35 | "anstyle",
36 | "anstyle-parse",
37 | "anstyle-query",
38 | "anstyle-wincon",
39 | "colorchoice",
40 | "utf8parse",
41 | ]
42 |
43 | [[package]]
44 | name = "anstyle"
45 | version = "1.0.4"
46 | source = "registry+https://github.com/rust-lang/crates.io-index"
47 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
48 |
49 | [[package]]
50 | name = "anstyle-parse"
51 | version = "0.2.3"
52 | source = "registry+https://github.com/rust-lang/crates.io-index"
53 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
54 | dependencies = [
55 | "utf8parse",
56 | ]
57 |
58 | [[package]]
59 | name = "anstyle-query"
60 | version = "1.0.2"
61 | source = "registry+https://github.com/rust-lang/crates.io-index"
62 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
63 | dependencies = [
64 | "windows-sys 0.52.0",
65 | ]
66 |
67 | [[package]]
68 | name = "anstyle-wincon"
69 | version = "3.0.2"
70 | source = "registry+https://github.com/rust-lang/crates.io-index"
71 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
72 | dependencies = [
73 | "anstyle",
74 | "windows-sys 0.52.0",
75 | ]
76 |
77 | [[package]]
78 | name = "anyhow"
79 | version = "1.0.79"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
82 |
83 | [[package]]
84 | name = "async-channel"
85 | version = "2.1.1"
86 | source = "registry+https://github.com/rust-lang/crates.io-index"
87 | checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
88 | dependencies = [
89 | "concurrent-queue",
90 | "event-listener",
91 | "event-listener-strategy",
92 | "futures-core",
93 | "pin-project-lite",
94 | ]
95 |
96 | [[package]]
97 | name = "async-stream"
98 | version = "0.3.5"
99 | source = "registry+https://github.com/rust-lang/crates.io-index"
100 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
101 | dependencies = [
102 | "async-stream-impl",
103 | "futures-core",
104 | "pin-project-lite",
105 | ]
106 |
107 | [[package]]
108 | name = "async-stream-impl"
109 | version = "0.3.5"
110 | source = "registry+https://github.com/rust-lang/crates.io-index"
111 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
112 | dependencies = [
113 | "proc-macro2",
114 | "quote",
115 | "syn 2.0.48",
116 | ]
117 |
118 | [[package]]
119 | name = "atty"
120 | version = "0.2.14"
121 | source = "registry+https://github.com/rust-lang/crates.io-index"
122 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
123 | dependencies = [
124 | "hermit-abi 0.1.19",
125 | "libc",
126 | "winapi",
127 | ]
128 |
129 | [[package]]
130 | name = "autocfg"
131 | version = "1.1.0"
132 | source = "registry+https://github.com/rust-lang/crates.io-index"
133 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
134 |
135 | [[package]]
136 | name = "backtrace"
137 | version = "0.3.69"
138 | source = "registry+https://github.com/rust-lang/crates.io-index"
139 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
140 | dependencies = [
141 | "addr2line",
142 | "cc",
143 | "cfg-if",
144 | "libc",
145 | "miniz_oxide",
146 | "object",
147 | "rustc-demangle",
148 | ]
149 |
150 | [[package]]
151 | name = "bitflags"
152 | version = "1.3.2"
153 | source = "registry+https://github.com/rust-lang/crates.io-index"
154 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
155 |
156 | [[package]]
157 | name = "bitflags"
158 | version = "2.4.1"
159 | source = "registry+https://github.com/rust-lang/crates.io-index"
160 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
161 |
162 | [[package]]
163 | name = "bytes"
164 | version = "1.5.0"
165 | source = "registry+https://github.com/rust-lang/crates.io-index"
166 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
167 |
168 | [[package]]
169 | name = "cc"
170 | version = "1.0.83"
171 | source = "registry+https://github.com/rust-lang/crates.io-index"
172 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
173 | dependencies = [
174 | "libc",
175 | ]
176 |
177 | [[package]]
178 | name = "cfg-if"
179 | version = "1.0.0"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
182 |
183 | [[package]]
184 | name = "clap"
185 | version = "4.4.16"
186 | source = "registry+https://github.com/rust-lang/crates.io-index"
187 | checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445"
188 | dependencies = [
189 | "clap_builder",
190 | "clap_derive",
191 | ]
192 |
193 | [[package]]
194 | name = "clap_builder"
195 | version = "4.4.16"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb"
198 | dependencies = [
199 | "anstream",
200 | "anstyle",
201 | "clap_lex",
202 | "strsim",
203 | ]
204 |
205 | [[package]]
206 | name = "clap_derive"
207 | version = "4.4.7"
208 | source = "registry+https://github.com/rust-lang/crates.io-index"
209 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
210 | dependencies = [
211 | "heck",
212 | "proc-macro2",
213 | "quote",
214 | "syn 2.0.48",
215 | ]
216 |
217 | [[package]]
218 | name = "clap_lex"
219 | version = "0.6.0"
220 | source = "registry+https://github.com/rust-lang/crates.io-index"
221 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
222 |
223 | [[package]]
224 | name = "colorchoice"
225 | version = "1.0.0"
226 | source = "registry+https://github.com/rust-lang/crates.io-index"
227 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
228 |
229 | [[package]]
230 | name = "concurrent-queue"
231 | version = "2.4.0"
232 | source = "registry+https://github.com/rust-lang/crates.io-index"
233 | checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
234 | dependencies = [
235 | "crossbeam-utils",
236 | ]
237 |
238 | [[package]]
239 | name = "crossbeam-utils"
240 | version = "0.8.16"
241 | source = "registry+https://github.com/rust-lang/crates.io-index"
242 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
243 | dependencies = [
244 | "cfg-if",
245 | ]
246 |
247 | [[package]]
248 | name = "deranged"
249 | version = "0.3.11"
250 | source = "registry+https://github.com/rust-lang/crates.io-index"
251 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
252 | dependencies = [
253 | "powerfmt",
254 | ]
255 |
256 | [[package]]
257 | name = "dnstap-utils"
258 | version = "0.5.0"
259 | dependencies = [
260 | "anyhow",
261 | "async-channel",
262 | "async-stream",
263 | "bytes",
264 | "clap",
265 | "domain",
266 | "futures",
267 | "futures-util",
268 | "heck",
269 | "hex",
270 | "hyper",
271 | "inotify",
272 | "ip_network",
273 | "ip_network_table",
274 | "lazy_static",
275 | "libc",
276 | "log",
277 | "prometheus",
278 | "prometheus-static-metric",
279 | "prost",
280 | "prost-build",
281 | "stderrlog",
282 | "thiserror",
283 | "time",
284 | "tokio",
285 | "tokio-stream",
286 | "tokio-util",
287 | ]
288 |
289 | [[package]]
290 | name = "domain"
291 | version = "0.9.3"
292 | source = "registry+https://github.com/rust-lang/crates.io-index"
293 | checksum = "e853e3f6d4c6e52a4d73a94c1810c66ad71958fbe24934a7119b447f425aed76"
294 | dependencies = [
295 | "bytes",
296 | "octseq",
297 | "rand",
298 | "time",
299 | ]
300 |
301 | [[package]]
302 | name = "either"
303 | version = "1.9.0"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
306 |
307 | [[package]]
308 | name = "errno"
309 | version = "0.3.8"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
312 | dependencies = [
313 | "libc",
314 | "windows-sys 0.52.0",
315 | ]
316 |
317 | [[package]]
318 | name = "event-listener"
319 | version = "4.0.3"
320 | source = "registry+https://github.com/rust-lang/crates.io-index"
321 | checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
322 | dependencies = [
323 | "concurrent-queue",
324 | "parking",
325 | "pin-project-lite",
326 | ]
327 |
328 | [[package]]
329 | name = "event-listener-strategy"
330 | version = "0.4.0"
331 | source = "registry+https://github.com/rust-lang/crates.io-index"
332 | checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
333 | dependencies = [
334 | "event-listener",
335 | "pin-project-lite",
336 | ]
337 |
338 | [[package]]
339 | name = "fastrand"
340 | version = "2.0.1"
341 | source = "registry+https://github.com/rust-lang/crates.io-index"
342 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
343 |
344 | [[package]]
345 | name = "fixedbitset"
346 | version = "0.4.2"
347 | source = "registry+https://github.com/rust-lang/crates.io-index"
348 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
349 |
350 | [[package]]
351 | name = "fnv"
352 | version = "1.0.7"
353 | source = "registry+https://github.com/rust-lang/crates.io-index"
354 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
355 |
356 | [[package]]
357 | name = "futures"
358 | version = "0.3.30"
359 | source = "registry+https://github.com/rust-lang/crates.io-index"
360 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
361 | dependencies = [
362 | "futures-channel",
363 | "futures-core",
364 | "futures-executor",
365 | "futures-io",
366 | "futures-sink",
367 | "futures-task",
368 | "futures-util",
369 | ]
370 |
371 | [[package]]
372 | name = "futures-channel"
373 | version = "0.3.30"
374 | source = "registry+https://github.com/rust-lang/crates.io-index"
375 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
376 | dependencies = [
377 | "futures-core",
378 | "futures-sink",
379 | ]
380 |
381 | [[package]]
382 | name = "futures-core"
383 | version = "0.3.30"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
386 |
387 | [[package]]
388 | name = "futures-executor"
389 | version = "0.3.30"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
392 | dependencies = [
393 | "futures-core",
394 | "futures-task",
395 | "futures-util",
396 | ]
397 |
398 | [[package]]
399 | name = "futures-io"
400 | version = "0.3.30"
401 | source = "registry+https://github.com/rust-lang/crates.io-index"
402 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
403 |
404 | [[package]]
405 | name = "futures-macro"
406 | version = "0.3.30"
407 | source = "registry+https://github.com/rust-lang/crates.io-index"
408 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
409 | dependencies = [
410 | "proc-macro2",
411 | "quote",
412 | "syn 2.0.48",
413 | ]
414 |
415 | [[package]]
416 | name = "futures-sink"
417 | version = "0.3.30"
418 | source = "registry+https://github.com/rust-lang/crates.io-index"
419 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
420 |
421 | [[package]]
422 | name = "futures-task"
423 | version = "0.3.30"
424 | source = "registry+https://github.com/rust-lang/crates.io-index"
425 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
426 |
427 | [[package]]
428 | name = "futures-util"
429 | version = "0.3.30"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
432 | dependencies = [
433 | "futures-channel",
434 | "futures-core",
435 | "futures-io",
436 | "futures-macro",
437 | "futures-sink",
438 | "futures-task",
439 | "memchr",
440 | "pin-project-lite",
441 | "pin-utils",
442 | "slab",
443 | ]
444 |
445 | [[package]]
446 | name = "getrandom"
447 | version = "0.2.11"
448 | source = "registry+https://github.com/rust-lang/crates.io-index"
449 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
450 | dependencies = [
451 | "cfg-if",
452 | "libc",
453 | "wasi",
454 | ]
455 |
456 | [[package]]
457 | name = "gimli"
458 | version = "0.28.1"
459 | source = "registry+https://github.com/rust-lang/crates.io-index"
460 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
461 |
462 | [[package]]
463 | name = "hashbrown"
464 | version = "0.12.3"
465 | source = "registry+https://github.com/rust-lang/crates.io-index"
466 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
467 |
468 | [[package]]
469 | name = "heck"
470 | version = "0.4.1"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
473 |
474 | [[package]]
475 | name = "hermit-abi"
476 | version = "0.1.19"
477 | source = "registry+https://github.com/rust-lang/crates.io-index"
478 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
479 | dependencies = [
480 | "libc",
481 | ]
482 |
483 | [[package]]
484 | name = "hermit-abi"
485 | version = "0.3.2"
486 | source = "registry+https://github.com/rust-lang/crates.io-index"
487 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
488 |
489 | [[package]]
490 | name = "hex"
491 | version = "0.4.3"
492 | source = "registry+https://github.com/rust-lang/crates.io-index"
493 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
494 |
495 | [[package]]
496 | name = "home"
497 | version = "0.5.5"
498 | source = "registry+https://github.com/rust-lang/crates.io-index"
499 | checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
500 | dependencies = [
501 | "windows-sys 0.48.0",
502 | ]
503 |
504 | [[package]]
505 | name = "http"
506 | version = "0.2.11"
507 | source = "registry+https://github.com/rust-lang/crates.io-index"
508 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
509 | dependencies = [
510 | "bytes",
511 | "fnv",
512 | "itoa",
513 | ]
514 |
515 | [[package]]
516 | name = "http-body"
517 | version = "0.4.6"
518 | source = "registry+https://github.com/rust-lang/crates.io-index"
519 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
520 | dependencies = [
521 | "bytes",
522 | "http",
523 | "pin-project-lite",
524 | ]
525 |
526 | [[package]]
527 | name = "httparse"
528 | version = "1.8.0"
529 | source = "registry+https://github.com/rust-lang/crates.io-index"
530 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
531 |
532 | [[package]]
533 | name = "httpdate"
534 | version = "1.0.3"
535 | source = "registry+https://github.com/rust-lang/crates.io-index"
536 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
537 |
538 | [[package]]
539 | name = "hyper"
540 | version = "0.14.28"
541 | source = "registry+https://github.com/rust-lang/crates.io-index"
542 | checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
543 | dependencies = [
544 | "bytes",
545 | "futures-channel",
546 | "futures-core",
547 | "futures-util",
548 | "http",
549 | "http-body",
550 | "httparse",
551 | "httpdate",
552 | "itoa",
553 | "pin-project-lite",
554 | "socket2",
555 | "tokio",
556 | "tower-service",
557 | "tracing",
558 | "want",
559 | ]
560 |
561 | [[package]]
562 | name = "indexmap"
563 | version = "1.9.3"
564 | source = "registry+https://github.com/rust-lang/crates.io-index"
565 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
566 | dependencies = [
567 | "autocfg",
568 | "hashbrown",
569 | ]
570 |
571 | [[package]]
572 | name = "inotify"
573 | version = "0.10.2"
574 | source = "registry+https://github.com/rust-lang/crates.io-index"
575 | checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
576 | dependencies = [
577 | "bitflags 1.3.2",
578 | "futures-core",
579 | "inotify-sys",
580 | "libc",
581 | "tokio",
582 | ]
583 |
584 | [[package]]
585 | name = "inotify-sys"
586 | version = "0.1.5"
587 | source = "registry+https://github.com/rust-lang/crates.io-index"
588 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
589 | dependencies = [
590 | "libc",
591 | ]
592 |
593 | [[package]]
594 | name = "ip_network"
595 | version = "0.4.1"
596 | source = "registry+https://github.com/rust-lang/crates.io-index"
597 | checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1"
598 |
599 | [[package]]
600 | name = "ip_network_table"
601 | version = "0.2.0"
602 | source = "registry+https://github.com/rust-lang/crates.io-index"
603 | checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0"
604 | dependencies = [
605 | "ip_network",
606 | "ip_network_table-deps-treebitmap",
607 | ]
608 |
609 | [[package]]
610 | name = "ip_network_table-deps-treebitmap"
611 | version = "0.5.0"
612 | source = "registry+https://github.com/rust-lang/crates.io-index"
613 | checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d"
614 |
615 | [[package]]
616 | name = "itertools"
617 | version = "0.10.5"
618 | source = "registry+https://github.com/rust-lang/crates.io-index"
619 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
620 | dependencies = [
621 | "either",
622 | ]
623 |
624 | [[package]]
625 | name = "itoa"
626 | version = "1.0.10"
627 | source = "registry+https://github.com/rust-lang/crates.io-index"
628 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
629 |
630 | [[package]]
631 | name = "lazy_static"
632 | version = "1.4.0"
633 | source = "registry+https://github.com/rust-lang/crates.io-index"
634 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
635 |
636 | [[package]]
637 | name = "libc"
638 | version = "0.2.152"
639 | source = "registry+https://github.com/rust-lang/crates.io-index"
640 | checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
641 |
642 | [[package]]
643 | name = "linux-raw-sys"
644 | version = "0.4.12"
645 | source = "registry+https://github.com/rust-lang/crates.io-index"
646 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
647 |
648 | [[package]]
649 | name = "lock_api"
650 | version = "0.4.11"
651 | source = "registry+https://github.com/rust-lang/crates.io-index"
652 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
653 | dependencies = [
654 | "autocfg",
655 | "scopeguard",
656 | ]
657 |
658 | [[package]]
659 | name = "log"
660 | version = "0.4.20"
661 | source = "registry+https://github.com/rust-lang/crates.io-index"
662 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
663 |
664 | [[package]]
665 | name = "memchr"
666 | version = "2.7.1"
667 | source = "registry+https://github.com/rust-lang/crates.io-index"
668 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
669 |
670 | [[package]]
671 | name = "miniz_oxide"
672 | version = "0.7.1"
673 | source = "registry+https://github.com/rust-lang/crates.io-index"
674 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
675 | dependencies = [
676 | "adler",
677 | ]
678 |
679 | [[package]]
680 | name = "mio"
681 | version = "0.8.10"
682 | source = "registry+https://github.com/rust-lang/crates.io-index"
683 | checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
684 | dependencies = [
685 | "libc",
686 | "wasi",
687 | "windows-sys 0.48.0",
688 | ]
689 |
690 | [[package]]
691 | name = "multimap"
692 | version = "0.8.3"
693 | source = "registry+https://github.com/rust-lang/crates.io-index"
694 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
695 |
696 | [[package]]
697 | name = "num_cpus"
698 | version = "1.16.0"
699 | source = "registry+https://github.com/rust-lang/crates.io-index"
700 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
701 | dependencies = [
702 | "hermit-abi 0.3.2",
703 | "libc",
704 | ]
705 |
706 | [[package]]
707 | name = "object"
708 | version = "0.32.2"
709 | source = "registry+https://github.com/rust-lang/crates.io-index"
710 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
711 | dependencies = [
712 | "memchr",
713 | ]
714 |
715 | [[package]]
716 | name = "octseq"
717 | version = "0.3.2"
718 | source = "registry+https://github.com/rust-lang/crates.io-index"
719 | checksum = "d92b38a4aabbacf619b8083841713216e7668178422decfe06bbc70643024c5d"
720 | dependencies = [
721 | "bytes",
722 | ]
723 |
724 | [[package]]
725 | name = "once_cell"
726 | version = "1.19.0"
727 | source = "registry+https://github.com/rust-lang/crates.io-index"
728 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
729 |
730 | [[package]]
731 | name = "parking"
732 | version = "2.2.0"
733 | source = "registry+https://github.com/rust-lang/crates.io-index"
734 | checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
735 |
736 | [[package]]
737 | name = "parking_lot"
738 | version = "0.12.1"
739 | source = "registry+https://github.com/rust-lang/crates.io-index"
740 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
741 | dependencies = [
742 | "lock_api",
743 | "parking_lot_core",
744 | ]
745 |
746 | [[package]]
747 | name = "parking_lot_core"
748 | version = "0.9.9"
749 | source = "registry+https://github.com/rust-lang/crates.io-index"
750 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
751 | dependencies = [
752 | "cfg-if",
753 | "libc",
754 | "redox_syscall 0.4.1",
755 | "smallvec",
756 | "windows-targets 0.48.5",
757 | ]
758 |
759 | [[package]]
760 | name = "petgraph"
761 | version = "0.6.3"
762 | source = "registry+https://github.com/rust-lang/crates.io-index"
763 | checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
764 | dependencies = [
765 | "fixedbitset",
766 | "indexmap",
767 | ]
768 |
769 | [[package]]
770 | name = "pin-project-lite"
771 | version = "0.2.13"
772 | source = "registry+https://github.com/rust-lang/crates.io-index"
773 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
774 |
775 | [[package]]
776 | name = "pin-utils"
777 | version = "0.1.0"
778 | source = "registry+https://github.com/rust-lang/crates.io-index"
779 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
780 |
781 | [[package]]
782 | name = "powerfmt"
783 | version = "0.2.0"
784 | source = "registry+https://github.com/rust-lang/crates.io-index"
785 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
786 |
787 | [[package]]
788 | name = "ppv-lite86"
789 | version = "0.2.17"
790 | source = "registry+https://github.com/rust-lang/crates.io-index"
791 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
792 |
793 | [[package]]
794 | name = "prettyplease"
795 | version = "0.2.16"
796 | source = "registry+https://github.com/rust-lang/crates.io-index"
797 | checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
798 | dependencies = [
799 | "proc-macro2",
800 | "syn 2.0.48",
801 | ]
802 |
803 | [[package]]
804 | name = "proc-macro2"
805 | version = "1.0.76"
806 | source = "registry+https://github.com/rust-lang/crates.io-index"
807 | checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
808 | dependencies = [
809 | "unicode-ident",
810 | ]
811 |
812 | [[package]]
813 | name = "prometheus"
814 | version = "0.13.3"
815 | source = "registry+https://github.com/rust-lang/crates.io-index"
816 | checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
817 | dependencies = [
818 | "cfg-if",
819 | "fnv",
820 | "lazy_static",
821 | "memchr",
822 | "parking_lot",
823 | "protobuf",
824 | "thiserror",
825 | ]
826 |
827 | [[package]]
828 | name = "prometheus-static-metric"
829 | version = "0.5.1"
830 | source = "registry+https://github.com/rust-lang/crates.io-index"
831 | checksum = "f8f30cdb09c39930b8fa5e0f23cbb895ab3f766b187403a0ba0956fc1ef4f0e5"
832 | dependencies = [
833 | "lazy_static",
834 | "proc-macro2",
835 | "quote",
836 | "syn 1.0.109",
837 | ]
838 |
839 | [[package]]
840 | name = "prost"
841 | version = "0.12.3"
842 | source = "registry+https://github.com/rust-lang/crates.io-index"
843 | checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a"
844 | dependencies = [
845 | "bytes",
846 | "prost-derive",
847 | ]
848 |
849 | [[package]]
850 | name = "prost-build"
851 | version = "0.12.3"
852 | source = "registry+https://github.com/rust-lang/crates.io-index"
853 | checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
854 | dependencies = [
855 | "bytes",
856 | "heck",
857 | "itertools",
858 | "log",
859 | "multimap",
860 | "once_cell",
861 | "petgraph",
862 | "prettyplease",
863 | "prost",
864 | "prost-types",
865 | "regex",
866 | "syn 2.0.48",
867 | "tempfile",
868 | "which",
869 | ]
870 |
871 | [[package]]
872 | name = "prost-derive"
873 | version = "0.12.3"
874 | source = "registry+https://github.com/rust-lang/crates.io-index"
875 | checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
876 | dependencies = [
877 | "anyhow",
878 | "itertools",
879 | "proc-macro2",
880 | "quote",
881 | "syn 2.0.48",
882 | ]
883 |
884 | [[package]]
885 | name = "prost-types"
886 | version = "0.12.3"
887 | source = "registry+https://github.com/rust-lang/crates.io-index"
888 | checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e"
889 | dependencies = [
890 | "prost",
891 | ]
892 |
893 | [[package]]
894 | name = "protobuf"
895 | version = "2.28.0"
896 | source = "registry+https://github.com/rust-lang/crates.io-index"
897 | checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
898 |
899 | [[package]]
900 | name = "quote"
901 | version = "1.0.35"
902 | source = "registry+https://github.com/rust-lang/crates.io-index"
903 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
904 | dependencies = [
905 | "proc-macro2",
906 | ]
907 |
908 | [[package]]
909 | name = "rand"
910 | version = "0.8.5"
911 | source = "registry+https://github.com/rust-lang/crates.io-index"
912 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
913 | dependencies = [
914 | "libc",
915 | "rand_chacha",
916 | "rand_core",
917 | ]
918 |
919 | [[package]]
920 | name = "rand_chacha"
921 | version = "0.3.1"
922 | source = "registry+https://github.com/rust-lang/crates.io-index"
923 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
924 | dependencies = [
925 | "ppv-lite86",
926 | "rand_core",
927 | ]
928 |
929 | [[package]]
930 | name = "rand_core"
931 | version = "0.6.4"
932 | source = "registry+https://github.com/rust-lang/crates.io-index"
933 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
934 | dependencies = [
935 | "getrandom",
936 | ]
937 |
938 | [[package]]
939 | name = "redox_syscall"
940 | version = "0.3.5"
941 | source = "registry+https://github.com/rust-lang/crates.io-index"
942 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
943 | dependencies = [
944 | "bitflags 1.3.2",
945 | ]
946 |
947 | [[package]]
948 | name = "redox_syscall"
949 | version = "0.4.1"
950 | source = "registry+https://github.com/rust-lang/crates.io-index"
951 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
952 | dependencies = [
953 | "bitflags 1.3.2",
954 | ]
955 |
956 | [[package]]
957 | name = "regex"
958 | version = "1.10.2"
959 | source = "registry+https://github.com/rust-lang/crates.io-index"
960 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
961 | dependencies = [
962 | "aho-corasick",
963 | "memchr",
964 | "regex-automata",
965 | "regex-syntax",
966 | ]
967 |
968 | [[package]]
969 | name = "regex-automata"
970 | version = "0.4.3"
971 | source = "registry+https://github.com/rust-lang/crates.io-index"
972 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
973 | dependencies = [
974 | "aho-corasick",
975 | "memchr",
976 | "regex-syntax",
977 | ]
978 |
979 | [[package]]
980 | name = "regex-syntax"
981 | version = "0.8.2"
982 | source = "registry+https://github.com/rust-lang/crates.io-index"
983 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
984 |
985 | [[package]]
986 | name = "rustc-demangle"
987 | version = "0.1.23"
988 | source = "registry+https://github.com/rust-lang/crates.io-index"
989 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
990 |
991 | [[package]]
992 | name = "rustix"
993 | version = "0.38.30"
994 | source = "registry+https://github.com/rust-lang/crates.io-index"
995 | checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
996 | dependencies = [
997 | "bitflags 2.4.1",
998 | "errno",
999 | "libc",
1000 | "linux-raw-sys",
1001 | "windows-sys 0.52.0",
1002 | ]
1003 |
1004 | [[package]]
1005 | name = "scopeguard"
1006 | version = "1.2.0"
1007 | source = "registry+https://github.com/rust-lang/crates.io-index"
1008 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1009 |
1010 | [[package]]
1011 | name = "serde"
1012 | version = "1.0.195"
1013 | source = "registry+https://github.com/rust-lang/crates.io-index"
1014 | checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
1015 | dependencies = [
1016 | "serde_derive",
1017 | ]
1018 |
1019 | [[package]]
1020 | name = "serde_derive"
1021 | version = "1.0.195"
1022 | source = "registry+https://github.com/rust-lang/crates.io-index"
1023 | checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
1024 | dependencies = [
1025 | "proc-macro2",
1026 | "quote",
1027 | "syn 2.0.48",
1028 | ]
1029 |
1030 | [[package]]
1031 | name = "signal-hook-registry"
1032 | version = "1.4.1"
1033 | source = "registry+https://github.com/rust-lang/crates.io-index"
1034 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
1035 | dependencies = [
1036 | "libc",
1037 | ]
1038 |
1039 | [[package]]
1040 | name = "slab"
1041 | version = "0.4.9"
1042 | source = "registry+https://github.com/rust-lang/crates.io-index"
1043 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
1044 | dependencies = [
1045 | "autocfg",
1046 | ]
1047 |
1048 | [[package]]
1049 | name = "smallvec"
1050 | version = "1.11.2"
1051 | source = "registry+https://github.com/rust-lang/crates.io-index"
1052 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1053 |
1054 | [[package]]
1055 | name = "socket2"
1056 | version = "0.5.5"
1057 | source = "registry+https://github.com/rust-lang/crates.io-index"
1058 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
1059 | dependencies = [
1060 | "libc",
1061 | "windows-sys 0.48.0",
1062 | ]
1063 |
1064 | [[package]]
1065 | name = "stderrlog"
1066 | version = "0.5.4"
1067 | source = "registry+https://github.com/rust-lang/crates.io-index"
1068 | checksum = "69a26bbf6de627d389164afa9783739b56746c6c72c4ed16539f4ff54170327b"
1069 | dependencies = [
1070 | "atty",
1071 | "log",
1072 | "termcolor",
1073 | "thread_local",
1074 | ]
1075 |
1076 | [[package]]
1077 | name = "strsim"
1078 | version = "0.10.0"
1079 | source = "registry+https://github.com/rust-lang/crates.io-index"
1080 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1081 |
1082 | [[package]]
1083 | name = "syn"
1084 | version = "1.0.109"
1085 | source = "registry+https://github.com/rust-lang/crates.io-index"
1086 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1087 | dependencies = [
1088 | "proc-macro2",
1089 | "quote",
1090 | "unicode-ident",
1091 | ]
1092 |
1093 | [[package]]
1094 | name = "syn"
1095 | version = "2.0.48"
1096 | source = "registry+https://github.com/rust-lang/crates.io-index"
1097 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
1098 | dependencies = [
1099 | "proc-macro2",
1100 | "quote",
1101 | "unicode-ident",
1102 | ]
1103 |
1104 | [[package]]
1105 | name = "tempfile"
1106 | version = "3.8.0"
1107 | source = "registry+https://github.com/rust-lang/crates.io-index"
1108 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
1109 | dependencies = [
1110 | "cfg-if",
1111 | "fastrand",
1112 | "redox_syscall 0.3.5",
1113 | "rustix",
1114 | "windows-sys 0.48.0",
1115 | ]
1116 |
1117 | [[package]]
1118 | name = "termcolor"
1119 | version = "1.1.3"
1120 | source = "registry+https://github.com/rust-lang/crates.io-index"
1121 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1122 | dependencies = [
1123 | "winapi-util",
1124 | ]
1125 |
1126 | [[package]]
1127 | name = "thiserror"
1128 | version = "1.0.56"
1129 | source = "registry+https://github.com/rust-lang/crates.io-index"
1130 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
1131 | dependencies = [
1132 | "thiserror-impl",
1133 | ]
1134 |
1135 | [[package]]
1136 | name = "thiserror-impl"
1137 | version = "1.0.56"
1138 | source = "registry+https://github.com/rust-lang/crates.io-index"
1139 | checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
1140 | dependencies = [
1141 | "proc-macro2",
1142 | "quote",
1143 | "syn 2.0.48",
1144 | ]
1145 |
1146 | [[package]]
1147 | name = "thread_local"
1148 | version = "1.1.7"
1149 | source = "registry+https://github.com/rust-lang/crates.io-index"
1150 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
1151 | dependencies = [
1152 | "cfg-if",
1153 | "once_cell",
1154 | ]
1155 |
1156 | [[package]]
1157 | name = "time"
1158 | version = "0.3.31"
1159 | source = "registry+https://github.com/rust-lang/crates.io-index"
1160 | checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
1161 | dependencies = [
1162 | "deranged",
1163 | "itoa",
1164 | "powerfmt",
1165 | "serde",
1166 | "time-core",
1167 | "time-macros",
1168 | ]
1169 |
1170 | [[package]]
1171 | name = "time-core"
1172 | version = "0.1.2"
1173 | source = "registry+https://github.com/rust-lang/crates.io-index"
1174 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
1175 |
1176 | [[package]]
1177 | name = "time-macros"
1178 | version = "0.2.16"
1179 | source = "registry+https://github.com/rust-lang/crates.io-index"
1180 | checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
1181 | dependencies = [
1182 | "time-core",
1183 | ]
1184 |
1185 | [[package]]
1186 | name = "tokio"
1187 | version = "1.35.1"
1188 | source = "registry+https://github.com/rust-lang/crates.io-index"
1189 | checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
1190 | dependencies = [
1191 | "backtrace",
1192 | "bytes",
1193 | "libc",
1194 | "mio",
1195 | "num_cpus",
1196 | "parking_lot",
1197 | "pin-project-lite",
1198 | "signal-hook-registry",
1199 | "socket2",
1200 | "tokio-macros",
1201 | "windows-sys 0.48.0",
1202 | ]
1203 |
1204 | [[package]]
1205 | name = "tokio-macros"
1206 | version = "2.2.0"
1207 | source = "registry+https://github.com/rust-lang/crates.io-index"
1208 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
1209 | dependencies = [
1210 | "proc-macro2",
1211 | "quote",
1212 | "syn 2.0.48",
1213 | ]
1214 |
1215 | [[package]]
1216 | name = "tokio-stream"
1217 | version = "0.1.14"
1218 | source = "registry+https://github.com/rust-lang/crates.io-index"
1219 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
1220 | dependencies = [
1221 | "futures-core",
1222 | "pin-project-lite",
1223 | "tokio",
1224 | ]
1225 |
1226 | [[package]]
1227 | name = "tokio-util"
1228 | version = "0.7.10"
1229 | source = "registry+https://github.com/rust-lang/crates.io-index"
1230 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
1231 | dependencies = [
1232 | "bytes",
1233 | "futures-core",
1234 | "futures-sink",
1235 | "pin-project-lite",
1236 | "tokio",
1237 | "tracing",
1238 | ]
1239 |
1240 | [[package]]
1241 | name = "tower-service"
1242 | version = "0.3.2"
1243 | source = "registry+https://github.com/rust-lang/crates.io-index"
1244 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
1245 |
1246 | [[package]]
1247 | name = "tracing"
1248 | version = "0.1.40"
1249 | source = "registry+https://github.com/rust-lang/crates.io-index"
1250 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
1251 | dependencies = [
1252 | "pin-project-lite",
1253 | "tracing-core",
1254 | ]
1255 |
1256 | [[package]]
1257 | name = "tracing-core"
1258 | version = "0.1.32"
1259 | source = "registry+https://github.com/rust-lang/crates.io-index"
1260 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
1261 | dependencies = [
1262 | "once_cell",
1263 | ]
1264 |
1265 | [[package]]
1266 | name = "try-lock"
1267 | version = "0.2.5"
1268 | source = "registry+https://github.com/rust-lang/crates.io-index"
1269 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
1270 |
1271 | [[package]]
1272 | name = "unicode-ident"
1273 | version = "1.0.12"
1274 | source = "registry+https://github.com/rust-lang/crates.io-index"
1275 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
1276 |
1277 | [[package]]
1278 | name = "utf8parse"
1279 | version = "0.2.1"
1280 | source = "registry+https://github.com/rust-lang/crates.io-index"
1281 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
1282 |
1283 | [[package]]
1284 | name = "want"
1285 | version = "0.3.1"
1286 | source = "registry+https://github.com/rust-lang/crates.io-index"
1287 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
1288 | dependencies = [
1289 | "try-lock",
1290 | ]
1291 |
1292 | [[package]]
1293 | name = "wasi"
1294 | version = "0.11.0+wasi-snapshot-preview1"
1295 | source = "registry+https://github.com/rust-lang/crates.io-index"
1296 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1297 |
1298 | [[package]]
1299 | name = "which"
1300 | version = "4.4.2"
1301 | source = "registry+https://github.com/rust-lang/crates.io-index"
1302 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
1303 | dependencies = [
1304 | "either",
1305 | "home",
1306 | "once_cell",
1307 | "rustix",
1308 | ]
1309 |
1310 | [[package]]
1311 | name = "winapi"
1312 | version = "0.3.9"
1313 | source = "registry+https://github.com/rust-lang/crates.io-index"
1314 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1315 | dependencies = [
1316 | "winapi-i686-pc-windows-gnu",
1317 | "winapi-x86_64-pc-windows-gnu",
1318 | ]
1319 |
1320 | [[package]]
1321 | name = "winapi-i686-pc-windows-gnu"
1322 | version = "0.4.0"
1323 | source = "registry+https://github.com/rust-lang/crates.io-index"
1324 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1325 |
1326 | [[package]]
1327 | name = "winapi-util"
1328 | version = "0.1.5"
1329 | source = "registry+https://github.com/rust-lang/crates.io-index"
1330 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1331 | dependencies = [
1332 | "winapi",
1333 | ]
1334 |
1335 | [[package]]
1336 | name = "winapi-x86_64-pc-windows-gnu"
1337 | version = "0.4.0"
1338 | source = "registry+https://github.com/rust-lang/crates.io-index"
1339 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1340 |
1341 | [[package]]
1342 | name = "windows-sys"
1343 | version = "0.48.0"
1344 | source = "registry+https://github.com/rust-lang/crates.io-index"
1345 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1346 | dependencies = [
1347 | "windows-targets 0.48.5",
1348 | ]
1349 |
1350 | [[package]]
1351 | name = "windows-sys"
1352 | version = "0.52.0"
1353 | source = "registry+https://github.com/rust-lang/crates.io-index"
1354 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1355 | dependencies = [
1356 | "windows-targets 0.52.0",
1357 | ]
1358 |
1359 | [[package]]
1360 | name = "windows-targets"
1361 | version = "0.48.5"
1362 | source = "registry+https://github.com/rust-lang/crates.io-index"
1363 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
1364 | dependencies = [
1365 | "windows_aarch64_gnullvm 0.48.5",
1366 | "windows_aarch64_msvc 0.48.5",
1367 | "windows_i686_gnu 0.48.5",
1368 | "windows_i686_msvc 0.48.5",
1369 | "windows_x86_64_gnu 0.48.5",
1370 | "windows_x86_64_gnullvm 0.48.5",
1371 | "windows_x86_64_msvc 0.48.5",
1372 | ]
1373 |
1374 | [[package]]
1375 | name = "windows-targets"
1376 | version = "0.52.0"
1377 | source = "registry+https://github.com/rust-lang/crates.io-index"
1378 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
1379 | dependencies = [
1380 | "windows_aarch64_gnullvm 0.52.0",
1381 | "windows_aarch64_msvc 0.52.0",
1382 | "windows_i686_gnu 0.52.0",
1383 | "windows_i686_msvc 0.52.0",
1384 | "windows_x86_64_gnu 0.52.0",
1385 | "windows_x86_64_gnullvm 0.52.0",
1386 | "windows_x86_64_msvc 0.52.0",
1387 | ]
1388 |
1389 | [[package]]
1390 | name = "windows_aarch64_gnullvm"
1391 | version = "0.48.5"
1392 | source = "registry+https://github.com/rust-lang/crates.io-index"
1393 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
1394 |
1395 | [[package]]
1396 | name = "windows_aarch64_gnullvm"
1397 | version = "0.52.0"
1398 | source = "registry+https://github.com/rust-lang/crates.io-index"
1399 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
1400 |
1401 | [[package]]
1402 | name = "windows_aarch64_msvc"
1403 | version = "0.48.5"
1404 | source = "registry+https://github.com/rust-lang/crates.io-index"
1405 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1406 |
1407 | [[package]]
1408 | name = "windows_aarch64_msvc"
1409 | version = "0.52.0"
1410 | source = "registry+https://github.com/rust-lang/crates.io-index"
1411 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
1412 |
1413 | [[package]]
1414 | name = "windows_i686_gnu"
1415 | version = "0.48.5"
1416 | source = "registry+https://github.com/rust-lang/crates.io-index"
1417 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1418 |
1419 | [[package]]
1420 | name = "windows_i686_gnu"
1421 | version = "0.52.0"
1422 | source = "registry+https://github.com/rust-lang/crates.io-index"
1423 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
1424 |
1425 | [[package]]
1426 | name = "windows_i686_msvc"
1427 | version = "0.48.5"
1428 | source = "registry+https://github.com/rust-lang/crates.io-index"
1429 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1430 |
1431 | [[package]]
1432 | name = "windows_i686_msvc"
1433 | version = "0.52.0"
1434 | source = "registry+https://github.com/rust-lang/crates.io-index"
1435 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
1436 |
1437 | [[package]]
1438 | name = "windows_x86_64_gnu"
1439 | version = "0.48.5"
1440 | source = "registry+https://github.com/rust-lang/crates.io-index"
1441 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1442 |
1443 | [[package]]
1444 | name = "windows_x86_64_gnu"
1445 | version = "0.52.0"
1446 | source = "registry+https://github.com/rust-lang/crates.io-index"
1447 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
1448 |
1449 | [[package]]
1450 | name = "windows_x86_64_gnullvm"
1451 | version = "0.48.5"
1452 | source = "registry+https://github.com/rust-lang/crates.io-index"
1453 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1454 |
1455 | [[package]]
1456 | name = "windows_x86_64_gnullvm"
1457 | version = "0.52.0"
1458 | source = "registry+https://github.com/rust-lang/crates.io-index"
1459 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
1460 |
1461 | [[package]]
1462 | name = "windows_x86_64_msvc"
1463 | version = "0.48.5"
1464 | source = "registry+https://github.com/rust-lang/crates.io-index"
1465 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1466 |
1467 | [[package]]
1468 | name = "windows_x86_64_msvc"
1469 | version = "0.52.0"
1470 | source = "registry+https://github.com/rust-lang/crates.io-index"
1471 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
1472 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "dnstap-utils"
3 | version = "0.5.0"
4 | description = "dnstap utilities"
5 | authors = ["Fastly"]
6 | keywords = ["dns", "dnstap"]
7 | categories = ["command-line-utilities"]
8 | edition = "2021"
9 | repository = "https://github.com/fastly/dnstap-utils"
10 | license = "Apache-2.0"
11 | include = ["src/**/*", "LICENSE", "README.md", "dnstap.pb/dnstap.proto", "build.rs"]
12 |
13 | [dependencies]
14 | anyhow = "1.0.79"
15 | async-channel = "2.1.1"
16 | async-stream = "0.3.5"
17 | bytes = "1.5.0"
18 | clap = { version = "4.4.16", features = ["derive"] }
19 | domain = "0.9.3"
20 | futures = "0.3.30"
21 | futures-util = "0.3.30"
22 | heck = "0.4.1"
23 | hex = "0.4.3"
24 | hyper = { version = "0.14.28", features = ["server", "stream", "http1", "tcp"] }
25 | inotify = "0.10.2"
26 | ip_network = "0.4.1"
27 | ip_network_table = "0.2.0"
28 | lazy_static = "1.4.0"
29 | libc = "0.2.152"
30 | log = "0.4.20"
31 | prometheus = "0.13.3"
32 | prometheus-static-metric = "0.5.1"
33 | prost = "0.12.3"
34 | stderrlog = { version = "0.5.4", default-features = false }
35 | thiserror = "1.0.56"
36 | time = { version = "0.3.31", features = ["formatting", "macros"] }
37 | tokio = { version = "1.35.1", features = ["full"] }
38 | tokio-stream = "0.1.14"
39 | tokio-util = { version = "0.7.10", features = ["codec", "io"] }
40 |
41 | [build-dependencies]
42 | prost-build = "0.12.3"
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright 2021-2022 Fastly, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dnstap-utils
2 |
3 | A collection of [dnstap] utilities implemented using the Rust
4 | programming language.
5 |
6 | [dnstap]: https://dnstap.info/
7 |
8 | ## `dnstap-replay`
9 |
10 | `dnstap-replay` is a dnstap collection server which receives dnstap
11 | messages from one or more DNS nameservers and replays them against a
12 | target nameserver. The responses from the target nameserver are
13 | compared against the originally logged response messages and any
14 | ***mismatches*** or other errors are made available in dnstap format
15 | via an HTTP endpoint for later analysis.
16 |
17 | ### `dnstap-replay`: dnstap message requirements
18 |
19 | `dnstap-replay` was designed for testing authoritative nameservers. The
20 | only type of dnstap log payload that `dnstap-replay` supports is the
21 | `Message/AUTH_RESPONSE` type. Any other dnstap log payload types will be
22 | silently ignored by `dnstap-replay`.
23 |
24 | The following fields are ***required*** to be set in the dnstap log
25 | payload:
26 |
27 | * `query_address`
28 | * `query_port`
29 | * `query_message`
30 | * `response_message`
31 |
32 | Typically, dnstap `Message/*_RESPONSE` log payloads do not include both
33 | the `query_message` and `response_message` fields on the assumption that
34 | the query message will be logged separately by a `Message/*_QUERY` log
35 | payload. However, this presents a problem for the replay-and-comparison
36 | phase in `dnstap-replay` because it is not entirely trivial to derive
37 | the original DNS query message given only the DNS response message. In
38 | some cases it may be impossible to recover the original query, for
39 | instance if the query is not a validly formatted DNS message.
40 |
41 | For the Knot DNS server, [support was added in version 3.1.4] to add a
42 | configuration option `responses-with-queries` to the `dnstap` module
43 | that logs ***both*** query and response messages together in the
44 | `Message/AUTH_RESPONSE` log payload type. The `mod-dnstap` configuration
45 | stanza in `knot.conf` would need to look like the following to produce
46 | dnstap output with the fields needed by `dnstap-replay`:
47 |
48 | ```
49 | mod-dnstap:
50 | - id: "default"
51 | sink: "[...]"
52 | log-queries: off
53 | log-responses: on
54 | responses-with-queries: on
55 | ```
56 |
57 | [support was added in version 3.1.4]: https://gitlab.nic.cz/knot/knot-dns/-/issues/764
58 |
59 | ### `dnstap-replay`: PROXY support in target nameserver
60 |
61 | `dnstap-replay` was originally designed for testing nameservers that may
62 | have source IP address dependent behavior or configuration. When a
63 | dnstap-originated DNS query message is replayed by `dnstap-replay`, the
64 | target nameserver sees the source IP address of the machine running
65 | `dnstap-replay` on the UDP packets containing the replayed query
66 | messages. This may elicit varying DNS response message content from the
67 | target nameserver.
68 |
69 | In order to avoid this problem, `dnstap-replay` can use the haproxy
70 | [PROXY] protocol to prepend the original source address and source port
71 | as logged in the `query_address` and `query_port` dnstap message fields
72 | to the outgoing DNS query message sent to the target nameserver. This
73 | requires support in the target nameserver. Currently, at least
74 | [dnsdist], [PowerDNS Authoritative Nameserver], [PowerDNS Recursor], and
75 | [Knot DNS] have support for the PROXY header.
76 |
77 | To enable this functionality in `dnstap-replay`, add the `--proxy`
78 | option to the command-line parameters.
79 |
80 | Support for PROXYv2 as a connection target was added in Knot DNS version
81 | 3.2.2, which adds a configuration option [`proxy_allowlist`] that lists
82 | the IP addresses that are allowed to initiate queries with the PROXYv2
83 | header. It is enabled by placing the option in the `server`
84 | configuration stanza, for instance:
85 |
86 | ```
87 | server:
88 | […]
89 | proxy-allowlist: 127.0.0.0/8
90 | ```
91 |
92 | [PROXY]: https://www.haproxy.org/download/2.5/doc/proxy-protocol.txt
93 | [dnsdist]: https://blog.powerdns.com/2021/05/11/dnsdist-1-6-0-released/
94 | [PowerDNS Authoritative Nameserver]: https://github.com/PowerDNS/pdns/pull/10660
95 | [PowerDNS Recursor]: https://github.com/PowerDNS/pdns/pull/8874
96 | [Knot DNS]: https://gitlab.nic.cz/knot/knot-dns/-/merge_requests/1468
97 | [`proxy_allowlist`]: https://www.knot-dns.cz/docs/3.2/html/reference.html?highlight=proxy#proxy-allowlist
98 |
99 | ### `dnstap-replay`: HTTP server
100 |
101 | `dnstap-replay` includes a built-in HTTP server to export [Prometheus
102 | metrics] which are available at the `/metrics` HTTP endpoint.
103 |
104 | When `dnstap-replay` sends a DNS query to the target nameserver and the
105 | response from the target nameserver does not exactly match the
106 | originally logged response message, a log message containing the
107 | mismatched response message is generated and buffered in memory and can
108 | be retrieved from the `/errors` HTTP endpoint. This endpoint drains the
109 | error buffer and provides the output in Frame Streams format containing
110 | dnstap payloads.
111 |
112 | The dnstap log messages exported via the `/errors` endpoint are the
113 | originally logged dnstap messages received by `dnstap-replay`, with the
114 | [dnstap `extra` field] populated with a serialized version of the error
115 | encountered by `dnstap-replay`. This preserves the original DNS response
116 | message as well as the DNS response message sent by the target
117 | nameserver, which allows for byte-for-byte analysis of the mismatch.
118 |
119 | A separate `/timeouts` endpoint is available which can be used to
120 | retrieve dnstap log messages that resulted in timeouts when re-querying
121 | the target nameserver. The format used is the same as the `/errors`
122 | endpoint.
123 |
124 | [Prometheus metrics]: https://github.com/fastly/dnstap-utils/blob/main/src/bin/dnstap-replay/metrics.rs
125 | [dnstap `extra` field]: https://github.com/dnstap/dnstap.pb/blob/9bafb5b59dacc48a6ff6a839e419e540f1201c42/dnstap.proto#L37-L40
126 |
127 | ### `dnstap-replay`: Command-line example
128 |
129 | `dnstap-replay` requires the `--dns`, `--http`, and `--unix` arguments
130 | to be provided.
131 |
132 | The `--dns` argument specifies the IP address and port of the target
133 | nameserver which will receive replayed DNS queries.
134 |
135 | The `--http` argument specifies the IP address and port for the built-in
136 | HTTP server.
137 |
138 | The `--unix` argument specifies the filesystem path to bind the dnstap
139 | Unix socket to.
140 |
141 | Additionally, there are command-line options `--channel-capacity` and
142 | `--channel-error-capacity` which allow tuning of internal buffer
143 | sizes.
144 |
145 | For example, the following command-line invocation will listen on the
146 | filesystem path `/run/dnstap.sock` for incoming dnstap connections from
147 | the DNS server(s) that will send dnstap log data and on the TCP socket
148 | 127.0.0.1:53080 for incoming HTTP connections. Replayed DNS queries will
149 | be sent to the target nameserver which should be configured to listen on
150 | 127.0.0.1:53053.
151 |
152 | ```
153 | $ dnstap-replay --dns 127.0.0.1:53053 --http 127.0.0.1:53080 --unix /run/dnstap.sock
154 | ```
155 |
156 | The Prometheus metrics endpoint can be accessed at
157 | `http://127.0.0.1:53080/metrics`.
158 |
159 | The Frame Streams "errors" endpoint can be accessed at
160 | `http://127.0.0.1:53080/errors`.
161 |
162 | The Frame Streams "timeouts" endpoint can be accessad at
163 | `http://127.0.0.1:53080/timeouts`.
164 |
165 | ## `dnstap-dump`
166 |
167 | `dnstap-dump` is a utility which dumps a Frame Streams formatted dnstap
168 | file to YAML. The output format is very similar to the format generated
169 | by the [`dnstap-ldns`] utility.
170 |
171 | It has support for decoding the `extra` field in dnstap error payloads
172 | produced by `dnstap-replay`, and it also dumps DNS wire messages in
173 | hex-encoded wire format as well as in dig-style output.
174 |
175 | [`dnstap-ldns`]: https://github.com/dnstap/dnstap-ldns
176 |
177 | ## `dnstap-inject`
178 |
179 | `dnstap-inject` is a utility which reads a Frame Streams formatted
180 | dnstap file, extracts messages which contain both a `query_address` and
181 | `query_message` field, and re-sends the query message to a DNS server.
182 | It can optionally prepend a PROXYv2 header to the query sent to the DNS
183 | server (if supported by the server) in order to exercise source address
184 | dependent behavior.
185 |
186 | ## `fmt-dns-message`
187 |
188 | `fmt-dns-message` is a utility which converts a hex-encoded wire format
189 | DNS message to dig-style output using the [NLnet Labs `domain` crate].
190 |
191 | [NLnet Labs `domain` crate]: https://github.com/NLnetLabs/domain
192 |
193 | ## License
194 |
195 | `dnstap-utils` is distributed under the terms of the [Apache-2.0]
196 | license. See the [LICENSE] and [NOTICE] files for details.
197 |
198 | [Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0
199 | [LICENSE]: https://github.com/fastly/dnstap-utils/blob/main/LICENSE
200 | [NOTICE]: https://github.com/fastly/dnstap-utils/blob/main/NOTICE
201 |
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Fastly, Inc.
2 |
3 | fn main() -> std::io::Result<()> {
4 | prost_build::compile_protos(&["dnstap.pb/dnstap.proto"], &["dnstap.pb/"])?;
5 | Ok(())
6 | }
7 |
--------------------------------------------------------------------------------
/dnstap.pb/dnstap.proto:
--------------------------------------------------------------------------------
1 | // dnstap: flexible, structured event replication format for DNS software
2 | //
3 | // This file contains the protobuf schemas for the "dnstap" structured event
4 | // replication format for DNS software.
5 |
6 | // Written in 2013-2014 by Farsight Security, Inc.
7 | //
8 | // To the extent possible under law, the author(s) have dedicated all
9 | // copyright and related and neighboring rights to this file to the public
10 | // domain worldwide. This file is distributed without any warranty.
11 | //
12 | // You should have received a copy of the CC0 Public Domain Dedication along
13 | // with this file. If not, see:
14 | //
15 | // .
16 |
17 | syntax = "proto2";
18 | package dnstap;
19 |
20 | // "Dnstap": this is the top-level dnstap type, which is a "union" type that
21 | // contains other kinds of dnstap payloads, although currently only one type
22 | // of dnstap payload is defined.
23 | // See: https://developers.google.com/protocol-buffers/docs/techniques#union
24 | message Dnstap {
25 | // DNS server identity.
26 | // If enabled, this is the identity string of the DNS server which generated
27 | // this message. Typically this would be the same string as returned by an
28 | // "NSID" (RFC 5001) query.
29 | optional bytes identity = 1;
30 |
31 | // DNS server version.
32 | // If enabled, this is the version string of the DNS server which generated
33 | // this message. Typically this would be the same string as returned by a
34 | // "version.bind" query.
35 | optional bytes version = 2;
36 |
37 | // Extra data for this payload.
38 | // This field can be used for adding an arbitrary byte-string annotation to
39 | // the payload. No encoding or interpretation is applied or enforced.
40 | optional bytes extra = 3;
41 |
42 | // Identifies which field below is filled in.
43 | enum Type {
44 | MESSAGE = 1;
45 | }
46 | required Type type = 15;
47 |
48 | // One of the following will be filled in.
49 | optional Message message = 14;
50 | }
51 |
52 | // SocketFamily: the network protocol family of a socket. This specifies how
53 | // to interpret "network address" fields.
54 | enum SocketFamily {
55 | INET = 1; // IPv4 (RFC 791)
56 | INET6 = 2; // IPv6 (RFC 2460)
57 | }
58 |
59 | // SocketProtocol: the protocol used to transport a DNS message.
60 | enum SocketProtocol {
61 | UDP = 1; // DNS over UDP transport (RFC 1035 section 4.2.1)
62 | TCP = 2; // DNS over TCP transport (RFC 1035 section 4.2.2)
63 | DOT = 3; // DNS over TLS (RFC 7858)
64 | DOH = 4; // DNS over HTTPS (RFC 8484)
65 | DNSCryptUDP = 5; // DNSCrypt over UDP (https://dnscrypt.info/protocol)
66 | DNSCryptTCP = 6; // DNSCrypt over TCP (https://dnscrypt.info/protocol)
67 | }
68 |
69 | // Policy: information about any name server operator policy
70 | // applied to the processing of a DNS message.
71 | message Policy {
72 |
73 | // Match: what aspect of the message or message exchange
74 | // triggered the application of the Policy.
75 | enum Match {
76 | QNAME = 1; // Name in question section of query
77 | CLIENT_IP = 2; // Client IP address
78 | RESPONSE_IP = 3; // Address in A/AAAA RRSet
79 | NS_NAME = 4; // Authoritative name server, by name
80 | NS_IP = 5; // Authoritative name server, by IP address
81 | }
82 |
83 | // The Action taken to implement the Policy.
84 | enum Action {
85 | NXDOMAIN = 1; // Respond with NXDOMAIN
86 | NODATA = 2; // Respond with empty answer section
87 | PASS = 3; // Do not alter the response (passthrough)
88 | DROP = 4; // Do not respond.
89 | TRUNCATE = 5; // Truncate UDP response, forcing TCP retry
90 | LOCAL_DATA = 6; // Respond with local data from policy
91 | }
92 |
93 | // type: the type of policy applied, e.g. "RPZ" for a
94 | // policy from a Response Policy Zone.
95 | optional string type = 1;
96 |
97 | // rule: the rule matched by the message.
98 | //
99 | // In a RPZ context, this is the owner name of the rule in
100 | // the Reponse Policy Zone in wire format.
101 | optional bytes rule = 2;
102 |
103 | // action: the policy action taken in response to the
104 | // rule match.
105 | optional Action action = 3;
106 |
107 | // match: the feature of the message exchange which matched the rule.
108 | optional Match match = 4;
109 |
110 | // The matched value. Format depends on the matched feature .
111 | optional bytes value = 5;
112 | }
113 |
114 | // Message: a wire-format (RFC 1035 section 4) DNS message and associated
115 | // metadata. Applications generating "Message" payloads should follow
116 | // certain requirements based on the MessageType, see below.
117 | message Message {
118 |
119 | // There are eight types of "Message" defined that correspond to the
120 | // four arrows in the following diagram, slightly modified from RFC 1035
121 | // section 2:
122 |
123 | // +---------+ +----------+ +--------+
124 | // | | query | | query | |
125 | // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. |
126 | // | Resolver| | Server | | Name |
127 | // | |<-SR--------CR-| |<-RR----AR-| Server |
128 | // +---------+ response | | response | |
129 | // +----------+ +--------+
130 |
131 | // Each arrow has two Type values each, one for each "end" of each arrow,
132 | // because these are considered to be distinct events. Each end of each
133 | // arrow on the diagram above has been marked with a two-letter Type
134 | // mnemonic. Clockwise from upper left, these mnemonic values are:
135 | //
136 | // SQ: STUB_QUERY
137 | // CQ: CLIENT_QUERY
138 | // RQ: RESOLVER_QUERY
139 | // AQ: AUTH_QUERY
140 | // AR: AUTH_RESPONSE
141 | // RR: RESOLVER_RESPONSE
142 | // CR: CLIENT_RESPONSE
143 | // SR: STUB_RESPONSE
144 |
145 | // Two additional types of "Message" have been defined for the
146 | // "forwarding" case where an upstream DNS server is responsible for
147 | // further recursion. These are not shown on the diagram above, but have
148 | // the following mnemonic values:
149 |
150 | // FQ: FORWARDER_QUERY
151 | // FR: FORWARDER_RESPONSE
152 |
153 | // The "Message" Type values are defined below.
154 |
155 | enum Type {
156 | // AUTH_QUERY is a DNS query message received from a resolver by an
157 | // authoritative name server, from the perspective of the authoritative
158 | // name server.
159 | AUTH_QUERY = 1;
160 |
161 | // AUTH_RESPONSE is a DNS response message sent from an authoritative
162 | // name server to a resolver, from the perspective of the authoritative
163 | // name server.
164 | AUTH_RESPONSE = 2;
165 |
166 | // RESOLVER_QUERY is a DNS query message sent from a resolver to an
167 | // authoritative name server, from the perspective of the resolver.
168 | // Resolvers typically clear the RD (recursion desired) bit when
169 | // sending queries.
170 | RESOLVER_QUERY = 3;
171 |
172 | // RESOLVER_RESPONSE is a DNS response message received from an
173 | // authoritative name server by a resolver, from the perspective of
174 | // the resolver.
175 | RESOLVER_RESPONSE = 4;
176 |
177 | // CLIENT_QUERY is a DNS query message sent from a client to a DNS
178 | // server which is expected to perform further recursion, from the
179 | // perspective of the DNS server. The client may be a stub resolver or
180 | // forwarder or some other type of software which typically sets the RD
181 | // (recursion desired) bit when querying the DNS server. The DNS server
182 | // may be a simple forwarding proxy or it may be a full recursive
183 | // resolver.
184 | CLIENT_QUERY = 5;
185 |
186 | // CLIENT_RESPONSE is a DNS response message sent from a DNS server to
187 | // a client, from the perspective of the DNS server. The DNS server
188 | // typically sets the RA (recursion available) bit when responding.
189 | CLIENT_RESPONSE = 6;
190 |
191 | // FORWARDER_QUERY is a DNS query message sent from a downstream DNS
192 | // server to an upstream DNS server which is expected to perform
193 | // further recursion, from the perspective of the downstream DNS
194 | // server.
195 | FORWARDER_QUERY = 7;
196 |
197 | // FORWARDER_RESPONSE is a DNS response message sent from an upstream
198 | // DNS server performing recursion to a downstream DNS server, from the
199 | // perspective of the downstream DNS server.
200 | FORWARDER_RESPONSE = 8;
201 |
202 | // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS
203 | // server, from the perspective of the stub resolver.
204 | STUB_QUERY = 9;
205 |
206 | // STUB_RESPONSE is a DNS response message sent from a DNS server to a
207 | // stub resolver, from the perspective of the stub resolver.
208 | STUB_RESPONSE = 10;
209 |
210 | // TOOL_QUERY is a DNS query message sent from a DNS software tool to a
211 | // DNS server, from the perspective of the tool.
212 | TOOL_QUERY = 11;
213 |
214 | // TOOL_RESPONSE is a DNS response message received by a DNS software
215 | // tool from a DNS server, from the perspective of the tool.
216 | TOOL_RESPONSE = 12;
217 |
218 | // UPDATE_QUERY is a DNS update query message received from a resolver
219 | // by an authoritative name server, from the perspective of the
220 | // authoritative name server.
221 | UPDATE_QUERY = 13;
222 |
223 | // UPDATE_RESPONSE is a DNS update response message sent from an
224 | // authoritative name server to a resolver, from the perspective of the
225 | // authoritative name server.
226 | UPDATE_RESPONSE = 14;
227 | }
228 |
229 | // One of the Type values described above.
230 | required Type type = 1;
231 |
232 | // One of the SocketFamily values described above.
233 | optional SocketFamily socket_family = 2;
234 |
235 | // One of the SocketProtocol values described above.
236 | optional SocketProtocol socket_protocol = 3;
237 |
238 | // The network address of the message initiator.
239 | // For SocketFamily INET, this field is 4 octets (IPv4 address).
240 | // For SocketFamily INET6, this field is 16 octets (IPv6 address).
241 | optional bytes query_address = 4;
242 |
243 | // The network address of the message responder.
244 | // For SocketFamily INET, this field is 4 octets (IPv4 address).
245 | // For SocketFamily INET6, this field is 16 octets (IPv6 address).
246 | optional bytes response_address = 5;
247 |
248 | // The transport port of the message initiator.
249 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
250 | optional uint32 query_port = 6;
251 |
252 | // The transport port of the message responder.
253 | // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
254 | optional uint32 response_port = 7;
255 |
256 | // The time at which the DNS query message was sent or received, depending
257 | // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY.
258 | // This is the number of seconds since the UNIX epoch.
259 | optional uint64 query_time_sec = 8;
260 |
261 | // The time at which the DNS query message was sent or received.
262 | // This is the seconds fraction, expressed as a count of nanoseconds.
263 | optional fixed32 query_time_nsec = 9;
264 |
265 | // The initiator's original wire-format DNS query message, verbatim.
266 | optional bytes query_message = 10;
267 |
268 | // The "zone" or "bailiwick" pertaining to the DNS query message.
269 | // This is a wire-format DNS domain name.
270 | optional bytes query_zone = 11;
271 |
272 | // The time at which the DNS response message was sent or received,
273 | // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or
274 | // CLIENT_RESPONSE.
275 | // This is the number of seconds since the UNIX epoch.
276 | optional uint64 response_time_sec = 12;
277 |
278 | // The time at which the DNS response message was sent or received.
279 | // This is the seconds fraction, expressed as a count of nanoseconds.
280 | optional fixed32 response_time_nsec = 13;
281 |
282 | // The responder's original wire-format DNS response message, verbatim.
283 | optional bytes response_message = 14;
284 |
285 | // Operator policy applied to the processing of this message, if any.
286 | optional Policy policy = 15;
287 | }
288 |
289 | // All fields except for 'type' in the Message schema are optional.
290 | // It is recommended that at least the following fields be filled in for
291 | // particular types of Messages.
292 |
293 | // AUTH_QUERY:
294 | // socket_family, socket_protocol
295 | // query_address, query_port
296 | // query_message
297 | // query_time_sec, query_time_nsec
298 |
299 | // AUTH_RESPONSE:
300 | // socket_family, socket_protocol
301 | // query_address, query_port
302 | // query_time_sec, query_time_nsec
303 | // response_message
304 | // response_time_sec, response_time_nsec
305 |
306 | // RESOLVER_QUERY:
307 | // socket_family, socket_protocol
308 | // query_message
309 | // query_time_sec, query_time_nsec
310 | // query_zone
311 | // response_address, response_port
312 |
313 | // RESOLVER_RESPONSE:
314 | // socket_family, socket_protocol
315 | // query_time_sec, query_time_nsec
316 | // query_zone
317 | // response_address, response_port
318 | // response_message
319 | // response_time_sec, response_time_nsec
320 |
321 | // CLIENT_QUERY:
322 | // socket_family, socket_protocol
323 | // query_message
324 | // query_time_sec, query_time_nsec
325 |
326 | // CLIENT_RESPONSE:
327 | // socket_family, socket_protocol
328 | // query_time_sec, query_time_nsec
329 | // response_message
330 | // response_time_sec, response_time_nsec
331 |
--------------------------------------------------------------------------------
/src/bin/dnstap-dump/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2024 Fastly, Inc.
2 |
3 | use anyhow::{bail, Result};
4 | use clap::{Parser, ValueHint};
5 | use heck::ToShoutySnekCase;
6 | use prost::Message;
7 | use std::fmt::Debug;
8 | use std::io::Write;
9 | use std::path::PathBuf;
10 | use tokio::fs::File;
11 | use tokio_stream::StreamExt;
12 | use tokio_util::codec::Framed;
13 |
14 | use dnstap_utils::dnstap;
15 | use dnstap_utils::framestreams_codec::{Frame, FrameStreamsCodec};
16 | use dnstap_utils::util::deserialize_dnstap_handler_error;
17 | use dnstap_utils::util::fmt_dns_message;
18 | use dnstap_utils::util::try_from_u8_slice_for_ipaddr;
19 | use dnstap_utils::util::unix_epoch_timestamp_to_string;
20 | use dnstap_utils::util::DnstapHandlerError;
21 |
22 | #[derive(Parser, Debug)]
23 | struct Opts {
24 | /// Read dnstap data from file
25 | #[clap(short = 'r',
26 | long = "read",
27 | name = "FILE",
28 | value_parser,
29 | value_hint = ValueHint::FilePath)
30 | ]
31 | file: PathBuf,
32 | }
33 |
34 | #[tokio::main]
35 | async fn main() -> Result<()> {
36 | // Workaround for https://github.com/rust-lang/rust/issues/62569
37 | if cfg!(unix) {
38 | unsafe {
39 | libc::signal(libc::SIGPIPE, libc::SIG_DFL);
40 | }
41 | }
42 |
43 | let opts = Opts::parse();
44 | let file = File::open(opts.file).await?;
45 | let mut framed = Framed::new(file, FrameStreamsCodec {});
46 |
47 | while let Some(frame) = framed.next().await {
48 | match frame {
49 | Ok(frame) => match frame {
50 | Frame::ControlReady(_) => {
51 | bail!("Protocol error: READY frame not allowed here");
52 | }
53 | Frame::ControlAccept(_) => {
54 | bail!("Protocol error: ACCEPT frame not allowed here");
55 | }
56 | Frame::ControlStart(_) => {
57 | // XXX: Validate the content type embedded in the Start control frame payload.
58 | }
59 | Frame::ControlStop => {
60 | return Ok(());
61 | }
62 | Frame::ControlFinish => {
63 | bail!("Protocol error: FINISH frame not allowed here");
64 | }
65 | Frame::ControlUnknown(_) => {
66 | bail!("Protocol error: Unknown control frame");
67 | }
68 | Frame::Data(mut payload) => match dnstap::Dnstap::decode(&mut payload) {
69 | Ok(d) => {
70 | let mut s = String::with_capacity(2048);
71 | fmt_dnstap(&mut s, d);
72 | s.push_str("---\n");
73 | std::io::stdout().write_all(s.as_bytes()).unwrap();
74 | }
75 | Err(e) => {
76 | bail!(
77 | "Protocol error: Decoding dnstap protobuf message: {}, payload: {}",
78 | e,
79 | hex::encode(&payload)
80 | );
81 | }
82 | },
83 | },
84 | Err(e) => {
85 | bail!("Protocol error: {}", e);
86 | }
87 | }
88 | }
89 |
90 | Ok(())
91 | }
92 |
93 | fn fmt_dnstap(s: &mut String, d: dnstap::Dnstap) {
94 | if let Ok(dtype) = dnstap::dnstap::Type::try_from(d.r#type) {
95 | s.push_str("type: ");
96 | s.push_str(&format!("{:?}", dtype).TO_SHOUTY_SNEK_CASE());
97 | s.push('\n');
98 | }
99 |
100 | if let Some(identity) = &d.identity {
101 | s.push_str("identity: \"");
102 | s.push_str(&String::from_utf8_lossy(identity));
103 | s.push_str("\"\n");
104 | }
105 |
106 | if let Some(version) = &d.version {
107 | s.push_str("version: \"");
108 | s.push_str(&String::from_utf8_lossy(version));
109 | s.push_str("\"\n");
110 | }
111 |
112 | if let Some(extra) = &d.extra {
113 | s.push_str("extra:\n");
114 |
115 | s.push_str(" bytes: \"");
116 | s.push_str(&hex::encode(extra));
117 | s.push_str("\"\n");
118 |
119 | s.push_str(" type: ");
120 |
121 | // Attempt to deserialize the dnstap payload's 'extra' field as if it were a serialized
122 | // DnstapHandler error. The 'extra' field in dnstap payloads "can be used for adding an
123 | // arbitrary byte-string annotation to the payload. No encoding or interpretation is
124 | // applied or enforced.", according to the dnstap protobuf definition.
125 | //
126 | // The custom serialization of DnstapHandler errors has a unique prefix which allows them
127 | // to be distinguished from other uses of the 'extra' field.
128 | match deserialize_dnstap_handler_error(extra) {
129 | Ok(dhe) => match dhe {
130 | DnstapHandlerError::Mismatch(mismatch_dns_bytes, _, _) => {
131 | s.push_str("DRDH_MISMATCH\n");
132 | s.push_str(" mismatch_bytes: \"");
133 | s.push_str(&hex::encode(&mismatch_dns_bytes));
134 | s.push_str("\"\n");
135 | s.push_str(" mismatch_formatted: |\n");
136 | fmt_dns_message(s, " ", &mismatch_dns_bytes);
137 | }
138 | DnstapHandlerError::Timeout => {
139 | s.push_str("DRDH_TIMEOUT\n");
140 | }
141 | DnstapHandlerError::MissingField => {
142 | s.push_str("DRDH_MISSING_FIELD\n");
143 | }
144 | },
145 | Err(_) => {
146 | s.push_str("DRDH_EXTRA_PAYLOAD_PARSE_ERROR\n");
147 | }
148 | }
149 | }
150 |
151 | if let Some(msg) = &d.message {
152 | fmt_dnstap_message(s, msg);
153 | }
154 | }
155 |
156 | fn fmt_dnstap_message(s: &mut String, msg: &dnstap::Message) {
157 | s.push_str("message:\n");
158 |
159 | s.push_str(" type: ");
160 | s.push_str(&format!("{:?}", msg.r#type()).TO_SHOUTY_SNEK_CASE());
161 | s.push('\n');
162 |
163 | if let Some(query_time_sec) = msg.query_time_sec {
164 | if let Ok(dt) = unix_epoch_timestamp_to_string(query_time_sec, msg.query_time_nsec) {
165 | s.push_str(" query_time: !!timestamp ");
166 | s.push_str(&dt);
167 | s.push('\n');
168 | }
169 | }
170 |
171 | if let Some(response_time_sec) = msg.response_time_sec {
172 | if let Ok(dt) = unix_epoch_timestamp_to_string(response_time_sec, msg.response_time_nsec) {
173 | s.push_str(" response_time: !!timestamp ");
174 | s.push_str(&dt);
175 | s.push('\n');
176 | }
177 | }
178 |
179 | if msg.socket_family.is_some() {
180 | s.push_str(" socket_family: ");
181 | s.push_str(&format!("{:?}", msg.socket_family()).TO_SHOUTY_SNEK_CASE());
182 | s.push('\n');
183 | }
184 |
185 | if msg.socket_protocol.is_some() {
186 | s.push_str(" socket_protocol: ");
187 | s.push_str(&format!("{:?}", msg.socket_protocol()).TO_SHOUTY_SNEK_CASE());
188 | s.push('\n');
189 | }
190 |
191 | if let Some(query_address) = &msg.query_address {
192 | if let Ok(query_address) = try_from_u8_slice_for_ipaddr(query_address) {
193 | s.push_str(" query_address: \"");
194 | s.push_str(&query_address.to_string());
195 | s.push_str("\"\n");
196 | }
197 | }
198 |
199 | if let Some(response_address) = &msg.response_address {
200 | if let Ok(response_address) = try_from_u8_slice_for_ipaddr(response_address) {
201 | s.push_str(" response_address: \"");
202 | s.push_str(&response_address.to_string());
203 | s.push_str("\"\n");
204 | }
205 | }
206 |
207 | if let Some(query_port) = &msg.query_port {
208 | s.push_str(" query_port: ");
209 | s.push_str(&query_port.to_string());
210 | s.push('\n');
211 | }
212 |
213 | if let Some(response_port) = &msg.response_port {
214 | s.push_str(" response_port: ");
215 | s.push_str(&response_port.to_string());
216 | s.push('\n');
217 | }
218 |
219 | if let Some(query_message) = &msg.query_message {
220 | s.push_str(" query_message_bytes: \"");
221 | s.push_str(&hex::encode(query_message));
222 | s.push_str("\"\n");
223 |
224 | s.push_str(" query_message_formatted: |\n");
225 | fmt_dns_message(s, " ", query_message);
226 | }
227 |
228 | if let Some(response_message) = &msg.response_message {
229 | s.push_str(" response_message_bytes: \"");
230 | s.push_str(&hex::encode(response_message));
231 | s.push_str("\"\n");
232 |
233 | s.push_str(" response_message_formatted: |\n");
234 | fmt_dns_message(s, " ", response_message);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/bin/dnstap-inject/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2024 Fastly, Inc.
2 |
3 | use anyhow::{bail, Result};
4 | use bytes::{BufMut, BytesMut};
5 | use clap::{ArgAction, Parser, ValueHint};
6 | use log::*;
7 | use prost::Message;
8 | use std::fmt::Debug;
9 | use std::net::{IpAddr, SocketAddr};
10 | use std::path::PathBuf;
11 | use std::time::Duration;
12 | use tokio::fs::File;
13 | use tokio::net::UdpSocket;
14 | use tokio::time::timeout;
15 | use tokio_stream::StreamExt;
16 | use tokio_util::codec::Framed;
17 |
18 | use dnstap_utils::dnstap;
19 | use dnstap_utils::framestreams_codec::{Frame, FrameStreamsCodec};
20 | use dnstap_utils::proxyv2;
21 | use dnstap_utils::util::try_from_u8_slice_for_ipaddr;
22 |
23 | /// Duration to wait for a response from the DNS server under test.
24 | const DNS_QUERY_TIMEOUT: Duration = Duration::from_millis(500);
25 |
26 | #[derive(Parser, Debug)]
27 | struct Opts {
28 | /// Read dnstap data from file
29 | #[clap(short = 'r',
30 | long = "read",
31 | name = "FILE",
32 | value_parser,
33 | value_hint = ValueHint::FilePath)
34 | ]
35 | file: PathBuf,
36 |
37 | /// UDP DNS server and port to send queries to
38 | #[clap(long, name = "DNS IP:PORT")]
39 | dns: SocketAddr,
40 |
41 | /// Whether to add PROXY v2 header to re-sent DNS queries
42 | #[clap(long)]
43 | proxy: bool,
44 |
45 | /// Increase verbosity level
46 | #[clap(short, long, action = ArgAction::Count)]
47 | verbose: u8,
48 | }
49 |
50 | #[tokio::main]
51 | async fn main() -> Result<()> {
52 | // Workaround for https://github.com/rust-lang/rust/issues/62569
53 | if cfg!(unix) {
54 | unsafe {
55 | libc::signal(libc::SIGPIPE, libc::SIG_DFL);
56 | }
57 | }
58 |
59 | let opts = Opts::parse();
60 |
61 | stderrlog::new()
62 | .verbosity(opts.verbose as usize)
63 | .module(module_path!())
64 | .init()
65 | .unwrap();
66 |
67 | let file = File::open(opts.file).await?;
68 | let socket = setup_socket(&opts.dns).await?;
69 |
70 | let mut framed = Framed::new(file, FrameStreamsCodec {});
71 | while let Some(frame) = framed.next().await {
72 | match frame {
73 | Ok(frame) => match frame {
74 | Frame::ControlReady(_) => {
75 | bail!("Protocol error: READY frame not allowed here");
76 | }
77 | Frame::ControlAccept(_) => {
78 | bail!("Protocol error: ACCEPT frame not allowed here");
79 | }
80 | Frame::ControlStart(_) => {
81 | // XXX: Validate the content type embedded in the Start control frame payload.
82 | }
83 | Frame::ControlStop => {
84 | return Ok(());
85 | }
86 | Frame::ControlFinish => {
87 | bail!("Protocol error: FINISH frame not allowed here");
88 | }
89 | Frame::ControlUnknown(_) => {
90 | bail!("Protocol error: Unknown control frame");
91 | }
92 | Frame::Data(mut payload) => match dnstap::Dnstap::decode(&mut payload) {
93 | Ok(d) => {
94 | process_dnstap_frame(&socket, opts.proxy, d).await?;
95 | }
96 | Err(e) => {
97 | bail!(
98 | "Protocol error: Decoding dnstap protobuf message: {}, payload: {}",
99 | e,
100 | hex::encode(&payload)
101 | );
102 | }
103 | },
104 | },
105 | Err(e) => {
106 | bail!("Protocol error: {}", e);
107 | }
108 | }
109 | }
110 |
111 | Ok(())
112 | }
113 |
114 | async fn setup_socket(server_addr: &SocketAddr) -> Result {
115 | let local_addr: SocketAddr = if server_addr.is_ipv4() {
116 | "0.0.0.0:0"
117 | } else {
118 | "[::]:0"
119 | }
120 | .parse()?;
121 |
122 | let socket = UdpSocket::bind(local_addr).await?;
123 | socket.connect(server_addr).await?;
124 | debug!("Connected socket to DNS server: {:?}", &socket);
125 | Ok(socket)
126 | }
127 |
128 | async fn process_dnstap_frame(socket: &UdpSocket, proxy: bool, d: dnstap::Dnstap) -> Result<()> {
129 | if let Ok(dtype) = dnstap::dnstap::Type::try_from(d.r#type) {
130 | if dtype == dnstap::dnstap::Type::Message {
131 | if let Some(msg) = &d.message {
132 | process_dnstap_message(socket, proxy, msg).await?;
133 | }
134 | }
135 | }
136 |
137 | Ok(())
138 | }
139 |
140 | async fn process_dnstap_message(
141 | socket: &UdpSocket,
142 | proxy: bool,
143 | msg: &dnstap::Message,
144 | ) -> Result<()> {
145 | if let Some(query_address) = &msg.query_address {
146 | if let Ok(query_address) = try_from_u8_slice_for_ipaddr(query_address) {
147 | if msg.query_message.is_some() {
148 | send_query(socket, proxy, &query_address, msg).await?;
149 | }
150 | }
151 | }
152 | Ok(())
153 | }
154 |
155 | async fn send_query(
156 | socket: &UdpSocket,
157 | proxy: bool,
158 | query_address: &IpAddr,
159 | msg: &dnstap::Message,
160 | ) -> Result<()> {
161 | if let Some(query_message) = &msg.query_message {
162 | // Buffer to received UDP response messages from the DNS server under test.
163 | let mut recv_buf: [u8; 4096] = [0; 4096];
164 |
165 | // Create a buffer for containing the original DNS query message, optionally with a PROXY v2
166 | // header prepended.
167 | let mut buf = BytesMut::with_capacity(1024);
168 |
169 | if proxy {
170 | proxyv2::add_proxy_payload(&mut buf, msg, query_address, None)?;
171 | }
172 |
173 | // Add the original DNS query message.
174 | buf.put_slice(query_message);
175 |
176 | // Freeze the buffer since it no longer needs to be mutated.
177 | let buf = buf.freeze();
178 |
179 | // Send the constructed query message to the DNS server under test.
180 | trace!("Sending DNS query: {}", hex::encode(query_message));
181 | socket.send(&buf).await?;
182 |
183 | // Receive the DNS response message from the DNS server under test, or wait for the DNS
184 | // query timeout to expire.
185 | match timeout(DNS_QUERY_TIMEOUT, socket.recv(&mut recv_buf)).await {
186 | Ok(res) => match res {
187 | Ok(n_bytes) => {
188 | let received_message = &recv_buf[..n_bytes];
189 | trace!("Received DNS response: {}", hex::encode(received_message));
190 | }
191 | Err(e) => {
192 | error!("Error while receiving response: {}", e);
193 | }
194 | },
195 | Err(e) => {
196 | error!("Timeout: {}", e);
197 | }
198 | }
199 | }
200 |
201 | Ok(())
202 | }
203 |
--------------------------------------------------------------------------------
/src/bin/dnstap-replay/dnstap_handler.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2024 Fastly, Inc.
2 |
3 | use anyhow::{bail, Result};
4 | use bytes::{BufMut, Bytes, BytesMut};
5 | use ip_network_table::IpNetworkTable;
6 | use log::*;
7 | use std::net::SocketAddr;
8 | use std::sync::atomic::{AtomicBool, Ordering};
9 | use std::sync::Arc;
10 | use std::time::Duration;
11 | use thiserror::Error;
12 | use tokio::net::UdpSocket;
13 | use tokio::time::timeout;
14 |
15 | use dnstap_utils::dnstap;
16 | use dnstap_utils::proxyv2;
17 | use dnstap_utils::util::dns_message_is_truncated;
18 | use dnstap_utils::util::try_from_u8_slice_for_ipaddr;
19 | use dnstap_utils::util::DnstapHandlerError;
20 |
21 | use crate::{Channels, Opts};
22 |
23 | /// Duration for [`DnstapHandler`]'s to wait for a response from the DNS server under test.
24 | const DNS_QUERY_TIMEOUT: Duration = Duration::from_millis(5000);
25 |
26 | /// Maximum UDP response message size that can be received from the DNS server under test.
27 | const DNS_RESPONSE_BUFFER_SIZE: usize = 4096;
28 |
29 | /// Process the dnstap protobuf payloads and re-send them to the DNS server under test.
30 | ///
31 | /// Decoded protobuf payloads are received over a channel from a [`crate::FrameHandler`]. The
32 | /// original address of the DNS client that sent the DNS query is used to construct a PROXY v2
33 | /// header which is prepended to the DNS query which is sent to the DNS server specified in the
34 | /// configuration. The response that the DNS server sends is then compared to the original DNS
35 | /// response message included in the dnstap payload and logged if they differ.
36 | ///
37 | /// This requires two specializations from the DNS server that we receive dnstap logging data and
38 | /// the DNS server that we re-send DNS queries to:
39 | ///
40 | /// 1. The DNS server that sends the dnstap payloads needs to produce `AUTH_RESPONSE` messages that
41 | /// include both the `query_message` and `response_message` fields. It is optional to fill out
42 | /// both of these fields, and DNS servers typically only fill out the `response_message` field
43 | /// for `AUTH_RESPONSE` dnstap payloads.
44 | /// 2. The DNS server under test needs to understand the PROXY v2 header which the
45 | /// [`DnstapHandler`] prepends to the DNS query. An unmodified DNS server will not recognize the
46 | /// prepended DNS queries that the [`DnstapHandler`] sends and will likely respond with the DNS
47 | /// `FORMERR` or `NOTIMP` RCODEs.
48 | pub struct DnstapHandler {
49 | /// Server options.
50 | opts: Opts,
51 |
52 | /// Server channels.
53 | channels: Channels,
54 |
55 | /// Networks to ignore queries from.
56 | ignore_query_nets: Option>,
57 |
58 | /// The result of the status monitor check. Controls whether mismatches should be emitted into
59 | /// the errors channel (if true) or suppressed (if false).
60 | match_status: Arc,
61 |
62 | /// Per-handler DNS client socket to use to send DNS queries to the DNS server under test. This
63 | /// is an [`Option`] rather than a [`UdpSocket`] because in the case of a timeout
64 | /// the socket will need to be closed and reopened.
65 | socket: Option,
66 |
67 | /// Buffer to received UDP response messages from the DNS server under test.
68 | recv_buf: [u8; DNS_RESPONSE_BUFFER_SIZE],
69 | }
70 |
71 | #[derive(Error, Debug)]
72 | enum DnstapHandlerInternalError {
73 | #[error("Non-UDP dnstap payload was discarded")]
74 | DiscardNonUdp,
75 | }
76 |
77 | impl DnstapHandler {
78 | /// Create a new [`DnstapHandler`] that receives decoded dnstap protobuf payloads from the
79 | /// `channel_receiver` channel, synthesizes new DNS client queries from the received dnstap
80 | /// payloads, and sends them to the DNS server under test.
81 | ///
82 | /// Server options are specified in `opts`, such as:
83 | ///
84 | /// * `opts.dns`: The DNS server address/port to send DNS queries to.
85 | /// * `opts.dscp`: The DSCP value to use for re-sent DNS queries.
86 | /// * `opts.proxy`: Whether to add PROXY v2 header to re-sent DNS queries.
87 | /// * `opts.ignore_tc`: Whether to ignore UDP responses with the TC bit set.
88 | ///
89 | /// The `match_status` variable is a shared flag used to suppress mismatches. It needs to be
90 | /// externally set to `true` to enable the generation of mismatch output and metrics.
91 | ///
92 | /// Responses received from the DNS server under test that don't match the original response in
93 | /// the dnstap payload will be sent via the channel `channel_error_sender` if `match_status` is
94 | /// `true`.
95 | ///
96 | /// If a DNS timeout occurs when re-querying the DNS server under test, the dnstap payload will
97 | /// be sent via the channel `channel_timeout_sender`.
98 | pub async fn new(
99 | opts: &Opts,
100 | channels: &Channels,
101 | match_status: Arc,
102 | ) -> Result {
103 | // Create the IpNetworkTable of networks to ignore queries from.
104 | let ignore_query_nets = if !opts.ignore_query_net.is_empty() {
105 | let mut table = IpNetworkTable::new();
106 | for net in &opts.ignore_query_net {
107 | table.insert(*net, true);
108 | }
109 | Some(table)
110 | } else {
111 | None
112 | };
113 |
114 | let mut handler = DnstapHandler {
115 | opts: opts.clone(),
116 | channels: channels.clone(),
117 | ignore_query_nets,
118 | match_status,
119 | socket: None,
120 | recv_buf: [0; DNS_RESPONSE_BUFFER_SIZE],
121 | };
122 |
123 | // Setup the private UDP client socket for this handler.
124 | handler.maybe_setup_socket().await?;
125 |
126 | Ok(handler)
127 | }
128 |
129 | /// Create, bind, and connect the UDP client socket if needed.
130 | async fn maybe_setup_socket(&mut self) -> Result<()> {
131 | if self.socket.is_none() {
132 | // Determine whether to create an IPv4 or IPv6 client socket.
133 | let local_address: SocketAddr = if self.opts.dns.is_ipv4() {
134 | "0.0.0.0:0"
135 | } else {
136 | "[::]:0"
137 | }
138 | .parse()?;
139 |
140 | // Bind the socket.
141 | let socket = UdpSocket::bind(local_address).await?;
142 |
143 | // Set the DSCP value.
144 | if let Some(dscp) = self.opts.dscp {
145 | set_udp_dscp(&socket, dscp)?;
146 | }
147 |
148 | // Connect the socket to the DNS server under test.
149 | socket.connect(&self.opts.dns).await?;
150 |
151 | debug!("Connected socket to DNS server: {:?}", &socket);
152 |
153 | // Store the socket for use by the main processing loop.
154 | self.socket = Some(socket);
155 | }
156 | Ok(())
157 | }
158 |
159 | /// Close and reopen the UDP client socket.
160 | async fn restart_socket(&mut self) -> Result<()> {
161 | // Drop the old socket.
162 | self.socket = None;
163 |
164 | // Setup the private UDP client socket for this handler again.
165 | self.maybe_setup_socket().await?;
166 |
167 | Ok(())
168 | }
169 |
170 | /// Receive dnstap protobuf payloads from a [`crate::FrameHandler`] and perform further
171 | /// processing.
172 | pub async fn run(&mut self) -> Result<()> {
173 | while let Ok(d) = self.channels.receiver.recv().await {
174 | // Check if the UDP client socket needs to be re-created.
175 | self.maybe_setup_socket().await?;
176 |
177 | // Actually process the dnstap payload.
178 | self.process_dnstap(d).await?
179 | }
180 | Ok(())
181 | }
182 |
183 | /// Process the outer dnstap container and if it contains a dnstap "Message" object of type
184 | /// `AUTH_RESPONSE`, perform further processing on it.
185 | async fn process_dnstap(&mut self, mut d: dnstap::Dnstap) -> Result<()> {
186 | // Currently only "Message" objects are defined.
187 | if dnstap::dnstap::Type::try_from(d.r#type) != Ok(dnstap::dnstap::Type::Message) {
188 | return Ok(());
189 | }
190 |
191 | let msg = match &d.message {
192 | Some(msg) => msg,
193 | None => return Ok(()),
194 | };
195 |
196 | // Check if this is an `AUTH_RESPONSE` type dnstap "Message" object.
197 | if dnstap::message::Type::try_from(msg.r#type) != Ok(dnstap::message::Type::AuthResponse) {
198 | return Ok(());
199 | }
200 |
201 | // Perform further processing on this message. On timeout, log the error and close and
202 | // reopen the UDP client socket.
203 | match self.process_dnstap_message(msg).await {
204 | Ok(_) => {
205 | crate::metrics::DNSTAP_PAYLOADS.success.inc();
206 | }
207 | Err(e) => {
208 | crate::metrics::DNSTAP_PAYLOADS.error.inc();
209 |
210 | if let Some(e) = e.downcast_ref::() {
211 | // Serialize the [`DnstapHandlerError`] instance and export it via the dnstap
212 | // object's `extra` field.
213 | d.extra = Some(e.serialize().to_vec());
214 |
215 | match e {
216 | DnstapHandlerError::Mismatch(_, _, _) => {
217 | // Send to the errors channel.
218 | self.send_error(d);
219 |
220 | crate::metrics::DNS_COMPARISONS.mismatched.inc();
221 | }
222 |
223 | DnstapHandlerError::Timeout => {
224 | // Send to the timeouts channel.
225 | self.send_timeout(d);
226 |
227 | crate::metrics::DNS_QUERIES.timeout.inc();
228 |
229 | // In the case of a DNS query timeout, we can't tell the difference
230 | // between a message that has genuinely been lost and one that might
231 | // still be processed by the DNS server under test and returned to us
232 | // on a subsequent socket read. If the latter happens, the lockstep
233 | // synchronization between a sent DNS query and a received DNS response
234 | // will be lost and every subsequent comparison between originally
235 | // logged response message and corresponding response message received
236 | // from the DNS server under test will fail because the wrong messages
237 | // are being compared.
238 | //
239 | // This kind of failure mode could be worked around by implementing a
240 | // state table for the outbound DNS queries sent to the DNS server
241 | // under test but this requires parsing some portions of the DNS header
242 | // and the question section as well as garbage collection of the state
243 | // table to avoid filling it with timed out queries. The easier thing
244 | // to do is to close the socket and open a new one.
245 | self.restart_socket().await?;
246 | }
247 |
248 | DnstapHandlerError::MissingField => {
249 | // Send to the errors channel.
250 | self.send_error(d);
251 |
252 | // Metric increment already handled above.
253 | }
254 | }
255 | } else if let Some(e) = e.downcast_ref::() {
256 | match e {
257 | DnstapHandlerInternalError::DiscardNonUdp => {
258 | crate::metrics::DNSTAP_HANDLER_INTERNAL_ERRORS
259 | .discard_non_udp
260 | .inc();
261 | }
262 | }
263 | }
264 | }
265 | }
266 |
267 | Ok(())
268 | }
269 |
270 | /// Process a dnstap "Message" object.
271 | async fn process_dnstap_message(&mut self, msg: &dnstap::Message) -> Result<()> {
272 | // Check if we have a connected UDP socket to send DNS queries to.
273 | let socket = match &self.socket {
274 | Some(socket) => socket,
275 | None => {
276 | bail!("No connected socket to send DNS queries");
277 | }
278 | };
279 |
280 | // Check if the original DNS query message was sent over UDP. If not, when the query is
281 | // re-sent over UDP it may elicit different behavior compared to the original transport.
282 | match &msg.socket_protocol {
283 | Some(socket_protocol) => {
284 | if dnstap::SocketProtocol::try_from(*socket_protocol)
285 | != Ok(dnstap::SocketProtocol::Udp)
286 | {
287 | bail!(DnstapHandlerInternalError::DiscardNonUdp);
288 | }
289 | }
290 | None => bail!(DnstapHandlerError::MissingField),
291 | };
292 |
293 | // Extract the `query_message` field. This is the original DNS query message sent to the
294 | // DNS server that logged the dnstap message.
295 | let query_message = match &msg.query_message {
296 | Some(msg) => msg,
297 | None => return Ok(()),
298 | };
299 |
300 | // Extract the `response_message` field. This is the original DNS response message sent by
301 | // the DNS server that logged the dnstap message to the original client.
302 | let response_message = match &msg.response_message {
303 | Some(msg) => msg,
304 | None => return Ok(()),
305 | };
306 |
307 | // Extract the `query_address` field and convert it to an [`IpAddr`]. This is the IP
308 | // address of the original client that sent the DNS query to the DNS server that logged the
309 | // dnstap message.
310 | let query_address = match &msg.query_address {
311 | Some(addr) => try_from_u8_slice_for_ipaddr(addr)?,
312 | None => bail!(DnstapHandlerError::MissingField),
313 | };
314 |
315 | // Check whether the query address matches any network in the table of networks to ignore
316 | // queries from.
317 | if let Some(table) = &self.ignore_query_nets {
318 | if table.longest_match(query_address).is_some() {
319 | crate::metrics::DNS_COMPARISONS.query_net_ignored.inc();
320 | return Ok(());
321 | };
322 | };
323 |
324 | // Create a buffer for containing the original DNS client message, optionally with a PROXY
325 | // v2 header prepended. The PROXY v2 payload that we generate will be small (<100 bytes)
326 | // and DNS query messages are restricted by the protocol to a maximum size of 512 bytes.
327 | let mut buf = BytesMut::with_capacity(1024);
328 |
329 | // Add the PROXY v2 payload, if the dnstap handler has been configured to do so.
330 | if self.opts.proxy {
331 | // Check if the dnstap handler has also been configured to add the timespec TLV to the
332 | // PROXY v2 payload.
333 | let timespec = if self.opts.proxy_timespec {
334 | Some(proxyv2::Timespec {
335 | seconds: msg.response_time_sec(),
336 | nanoseconds: msg.response_time_nsec(),
337 | })
338 | } else {
339 | None
340 | };
341 |
342 | proxyv2::add_proxy_payload(&mut buf, msg, &query_address, timespec)?;
343 | }
344 |
345 | // Add the original DNS query message.
346 | buf.put_slice(query_message);
347 |
348 | // Freeze the buffer since it no longer needs to be mutated.
349 | let buf = buf.freeze();
350 |
351 | // Send the constructed query message to the DNS server under test.
352 | trace!("Sending DNS query: {}", hex::encode(&buf));
353 | socket.send(&buf).await?;
354 |
355 | // Receive the DNS response message from the DNS server under test, or wait for the DNS
356 | // query timeout to expire.
357 | match timeout(DNS_QUERY_TIMEOUT, socket.recv(&mut self.recv_buf)).await {
358 | Ok(res) => match res {
359 | Ok(n_bytes) => {
360 | // A DNS response message was successfully received.
361 | crate::metrics::DNS_QUERIES.success.inc();
362 |
363 | let received_message = &self.recv_buf[..n_bytes];
364 | trace!("Received DNS response: {}", hex::encode(received_message));
365 |
366 | // Check if matching is enabled.
367 | if self.match_status.load(Ordering::Relaxed) {
368 | // Check if the DNS response message received from the DNS server under test is
369 | // identical to the original DNS response message recorded in the dnstap
370 | // message.
371 | if response_message == received_message {
372 | // Match.
373 | crate::metrics::DNS_COMPARISONS.matched.inc();
374 | } else if self.opts.ignore_tc
375 | && (dns_message_is_truncated(response_message)
376 | || dns_message_is_truncated(received_message))
377 | {
378 | // Either the original DNS response message or the DNS response message
379 | // received from the DNS server under test was truncated, and the
380 | // option to ignore TC=1 responses was enabled.
381 | crate::metrics::DNS_COMPARISONS.udp_tc_ignored.inc();
382 | } else {
383 | // Mismatch.
384 | bail!(DnstapHandlerError::Mismatch(
385 | Bytes::copy_from_slice(received_message),
386 | hex::encode(received_message),
387 | hex::encode(response_message),
388 | ));
389 | }
390 | } else {
391 | crate::metrics::DNS_COMPARISONS.suppressed.inc();
392 | }
393 | }
394 | Err(e) => {
395 | crate::metrics::DNS_QUERIES.error.inc();
396 | bail!(e);
397 | }
398 | },
399 | Err(_) => {
400 | bail!(DnstapHandlerError::Timeout);
401 | }
402 | }
403 |
404 | Ok(())
405 | }
406 |
407 | fn send_error(&self, d: dnstap::Dnstap) {
408 | match self.channels.error_sender.try_send(d) {
409 | Ok(_) => {
410 | crate::metrics::CHANNEL_ERROR_TX.success.inc();
411 | }
412 | Err(_) => {
413 | crate::metrics::CHANNEL_ERROR_TX.error.inc();
414 | }
415 | }
416 | }
417 |
418 | fn send_timeout(&self, d: dnstap::Dnstap) {
419 | match self.channels.timeout_sender.try_send(d) {
420 | Ok(_) => {
421 | crate::metrics::CHANNEL_TIMEOUT_TX.success.inc();
422 | }
423 | Err(_) => {
424 | crate::metrics::CHANNEL_TIMEOUT_TX.error.inc();
425 | }
426 | }
427 | }
428 | }
429 |
430 | /// Utility function that sets the DSCP value on a UDP socket.
431 | #[cfg(unix)]
432 | fn set_udp_dscp(s: &UdpSocket, dscp: u8) -> Result<()> {
433 | use std::os::unix::io::AsRawFd;
434 |
435 | let raw_fd = s.as_raw_fd();
436 | let optval: libc::c_int = (dscp << 2).into();
437 |
438 | let ret = match s.local_addr()? {
439 | SocketAddr::V4(_) => unsafe {
440 | libc::setsockopt(
441 | raw_fd,
442 | libc::IPPROTO_IP,
443 | libc::IP_TOS,
444 | &optval as *const _ as *const libc::c_void,
445 | std::mem::size_of_val(&optval) as libc::socklen_t,
446 | )
447 | },
448 | SocketAddr::V6(_) => unsafe {
449 | libc::setsockopt(
450 | raw_fd,
451 | libc::IPPROTO_IPV6,
452 | libc::IPV6_TCLASS,
453 | &optval as *const _ as *const libc::c_void,
454 | std::mem::size_of_val(&optval) as libc::socklen_t,
455 | )
456 | },
457 | };
458 |
459 | match ret {
460 | 0 => Ok(()),
461 | _ => bail!(
462 | "Failed to set DSCP value {} on socket fd {}: {}",
463 | dscp,
464 | raw_fd,
465 | std::io::Error::last_os_error()
466 | ),
467 | }
468 | }
469 |
470 | #[cfg(not(unix))]
471 | fn set_udp_dscp(_s: &UdpSocket, _dscp: u8) -> Result<()> {
472 | bail!("Cannot set DSCP values on this platform");
473 | }
474 |
--------------------------------------------------------------------------------
/src/bin/dnstap-replay/frame_handler.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Fastly, Inc.
2 |
3 | use anyhow::bail;
4 | use anyhow::Result;
5 | use async_channel::Sender;
6 | use futures::SinkExt;
7 | use log::*;
8 | use prost::Message;
9 | use std::os::unix::io::AsRawFd;
10 | use tokio::net::UnixStream;
11 | use tokio_stream::StreamExt;
12 | use tokio_util::codec::Framed;
13 |
14 | use dnstap_utils::dnstap;
15 | use dnstap_utils::framestreams_codec::{self, Frame, FrameStreamsCodec};
16 |
17 | /// Per-connection FrameStreams protocol handler. Reads delimited frames from the Unix socket
18 | /// stream, decodes the protobuf payload, and then sends the protobuf object over a channel to a
19 | /// [`crate::DnstapHandler`] for further processing.
20 | pub struct FrameHandler {
21 | /// The send side of the async channel, used by [`FrameHandler`]'s to send decoded dnstap
22 | /// protobuf messages to the [`crate::DnstapHandler`]'s.
23 | channel_sender: Sender,
24 |
25 | /// The Unix stream to read frames from.
26 | stream: UnixStream,
27 |
28 | /// Identifying description of the connected `stream`.
29 | stream_descr: String,
30 |
31 | /// Counter of the number of bytes processed by this [`FrameHandler`].
32 | count_data_bytes: usize,
33 |
34 | /// Counter of the number of frames processed by this [`FrameHandler`].
35 | count_data_frames: usize,
36 | }
37 |
38 | impl FrameHandler {
39 | /// Create a new [`FrameHandler`] that reads from `stream` and writes decoded protobuf messages
40 | /// to `channel_sender`.
41 | pub fn new(stream: UnixStream, channel_sender: Sender) -> Self {
42 | let stream_descr = format!("fd {}", stream.as_raw_fd());
43 |
44 | FrameHandler {
45 | stream,
46 | stream_descr,
47 | channel_sender,
48 | count_data_bytes: 0,
49 | count_data_frames: 0,
50 | }
51 | }
52 |
53 | /// Set up the FrameStreams connection and processing the incoming data frames.
54 | pub async fn run(&mut self) -> Result<()> {
55 | info!(
56 | "Accepted new Frame Streams connection on {}",
57 | self.stream_descr
58 | );
59 |
60 | // Initialize the FrameStreams codec on the connected stream.
61 | let mut framed = Framed::with_capacity(
62 | &mut self.stream,
63 | FrameStreamsCodec {},
64 | framestreams_codec::FRAME_LENGTH_MAX,
65 | );
66 |
67 | // Process each frame from the connection.
68 | while let Some(frame) = framed.next().await {
69 | match frame {
70 | Ok(frame) => {
71 | match frame {
72 | Frame::ControlReady(payload) => {
73 | // Ready: This is the first control frame received from the sender.
74 | // Send the Accept control frame.
75 | //
76 | // XXX: We mirror the content type(s) specified in the Ready control
77 | // frame payload into the Accept control frame payload. Instead we
78 | // should select a specific content type from the sender's list.
79 | framed.send(Frame::ControlAccept(payload)).await?;
80 | }
81 | Frame::ControlAccept(_) => {
82 | // Accept: This is the control frame that the receiver sends in
83 | // response to the Ready frame. It is a protocol violation for a sender
84 | // to send an Accept control frame.
85 | bail!(
86 | "{}: Protocol error: Sender sent ACCEPT frame",
87 | self.stream_descr
88 | );
89 | }
90 | Frame::ControlStart(payload) => {
91 | // Start: This is the control frame that the sender sends in response
92 | // to the Accept frame and indicates it will begin sending data frames
93 | // of the type specified in the Start control frame payload.
94 | //
95 | // XXX: We should probably do something with the content type that the
96 | // sender specifies in the Start control frame payload.
97 | trace!(
98 | "{}: START payload: {}",
99 | self.stream_descr,
100 | hex::encode(&payload)
101 | );
102 | }
103 | Frame::ControlStop => {
104 | // Stop: This is the control frame that the sender sends when it is
105 | // done sending Data frames. Send the Finish frame acknowledging
106 | // shutdown of the stream.
107 | info!(
108 | "{}: STOP received, processed {} data frames, {} data bytes",
109 | self.stream_descr, self.count_data_frames, self.count_data_bytes,
110 | );
111 | framed.send(Frame::ControlFinish).await?;
112 |
113 | // Shut the [`FrameHandler`] down.
114 | return Ok(());
115 | }
116 | Frame::ControlFinish => {
117 | // Protocol violation for a receiver to receive a Finish control frame.
118 | bail!(
119 | "{}: Protocol error: Sender sent FINISH frame",
120 | self.stream_descr
121 | );
122 | }
123 | Frame::ControlUnknown(_) => {
124 | bail!(
125 | "{}: Protocol error: Sender sent unknown control frame",
126 | self.stream_descr
127 | );
128 | }
129 | Frame::Data(mut payload) => {
130 | // Accounting.
131 | crate::metrics::DATA_FRAMES.inc();
132 | crate::metrics::DATA_BYTES.inc_by(payload.len() as u64);
133 | self.count_data_bytes += payload.len();
134 | self.count_data_frames += 1;
135 |
136 | // Decode the protobuf message.
137 | match dnstap::Dnstap::decode(&mut payload) {
138 | // The message was successfully parsed, send it to a
139 | // [`DnstapHandler`] for further processing.
140 | Ok(d) => match self.channel_sender.send(d).await {
141 | Ok(_) => {}
142 | Err(e) => {
143 | bail!("{}: Unable to send dnstap protobuf message to channel: {}",
144 | self.stream_descr, e);
145 | }
146 | },
147 | // The payload failed to parse.
148 | Err(e) => {
149 | bail!(
150 | "{}: Protocol error: Decoding dnstap protobuf message: {}, payload: {}",
151 | self.stream_descr,
152 | e,
153 | hex::encode(&payload)
154 | );
155 | }
156 | }
157 | }
158 | }
159 | }
160 | Err(e) => {
161 | bail!("{}: Protocol error: {}", self.stream_descr, e);
162 | }
163 | }
164 | }
165 |
166 | Ok(())
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/bin/dnstap-replay/http_handler.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2022 Fastly, Inc.
2 |
3 | use anyhow::Result;
4 | use async_channel::Receiver;
5 | use async_stream::stream;
6 | use bytes::{BufMut, BytesMut};
7 | use hyper::header::CONTENT_TYPE;
8 | use hyper::service::{make_service_fn, service_fn};
9 | use hyper::{Body, Request, Response};
10 | use hyper::{Method, StatusCode};
11 | use log::*;
12 | use prometheus::core::{AtomicU64, GenericCounter};
13 | use prometheus::Encoder as PrometheusEncoder;
14 | use prometheus::TextEncoder;
15 | use prost::Message;
16 | use std::convert::Infallible;
17 | use std::net::SocketAddr;
18 | use tokio_util::codec::Encoder as CodecEncoder;
19 |
20 | use dnstap_utils::dnstap;
21 | use dnstap_utils::framestreams_codec::{self, Frame, FrameStreamsCodec};
22 |
23 | use crate::Channels;
24 |
25 | /// Process HTTP requests.
26 | pub struct HttpHandler {
27 | /// HTTP server socket to listen on.
28 | http_address: SocketAddr,
29 |
30 | /// Server channels.
31 | channels: Channels,
32 | }
33 |
34 | /// Structure for encapsulating a channel together with the metric to be incremented when a payload
35 | /// is successfully read from the channel. Used below by the HTTP endpoints that read from the
36 | /// error and timeout channels.
37 | struct HttpChannel {
38 | receiver: Receiver,
39 | success_metric: &'static GenericCounter,
40 | }
41 |
42 | impl HttpHandler {
43 | /// Create a new [`HttpHandler`] that listens on `http_address`. For the `/errors`
44 | /// endpoint, error dnstap payloads will be retrieved from `channel_error_receiver`.
45 | pub fn new(http_address: SocketAddr, channels: &Channels) -> Self {
46 | HttpHandler {
47 | http_address,
48 | channels: channels.clone(),
49 | }
50 | }
51 |
52 | /// Run the HTTP server.
53 | pub async fn run(&self) -> Result<()> {
54 | // Clone the channels for the outer closure.
55 | let channel_error = self.channels.error_receiver.clone();
56 | let channel_timeout = self.channels.timeout_receiver.clone();
57 |
58 | let make_svc = make_service_fn(move |_| {
59 | // Clone the channels again for the inner closure.
60 | let channel_error = channel_error.clone();
61 | let channel_timeout = channel_timeout.clone();
62 |
63 | async move {
64 | Ok::<_, Infallible>(service_fn(move |req| {
65 | let channel_error = HttpChannel {
66 | receiver: channel_error.clone(),
67 | success_metric: &crate::metrics::CHANNEL_ERROR_RX.success,
68 | };
69 | let channel_timeout = HttpChannel {
70 | receiver: channel_timeout.clone(),
71 | success_metric: &crate::metrics::CHANNEL_TIMEOUT_RX.success,
72 | };
73 |
74 | async move { http_service(req, channel_error, channel_timeout).await }
75 | }))
76 | }
77 | });
78 |
79 | // Bind to the HTTP server address.
80 | let server = hyper::server::Server::try_bind(&self.http_address)?.serve(make_svc);
81 | info!("HTTP server listening on http://{}", &self.http_address);
82 |
83 | Ok(server.await?)
84 | }
85 | }
86 |
87 | /// Route HTTP requests based on method/path.
88 | async fn http_service(
89 | req: Request,
90 | channel_error: HttpChannel,
91 | channel_timeout: HttpChannel,
92 | ) -> Result> {
93 | match (req.method(), req.uri().path()) {
94 | // Handle the `/metrics` endpoint.
95 | (&Method::GET, "/metrics") => get_metrics_response(),
96 |
97 | // Handle the `/errors` endpoint.
98 | (&Method::GET, "/errors") => get_channel_response(channel_error),
99 |
100 | // Handle the `/timeouts` endpoint.
101 | (&Method::GET, "/timeouts") => get_channel_response(channel_timeout),
102 |
103 | // Default 404 Not Found response.
104 | _ => {
105 | let mut not_found = Response::default();
106 | *not_found.status_mut() = StatusCode::NOT_FOUND;
107 | Ok(not_found)
108 | }
109 | }
110 | }
111 |
112 | /// Handle requests for the Prometheus metrics endpoint.
113 | fn get_metrics_response() -> Result> {
114 | let encoder = TextEncoder::new();
115 |
116 | let metric_families = prometheus::gather();
117 | let mut buffer = vec![];
118 | encoder.encode(&metric_families, &mut buffer).unwrap();
119 |
120 | let response = Response::builder()
121 | .status(200)
122 | .header(CONTENT_TYPE, encoder.format_type())
123 | .body(Body::from(buffer))
124 | .unwrap();
125 |
126 | Ok(response)
127 | }
128 |
129 | /// Handle requests for the endpoints that return a Frame Streams formatted log file of dnstap
130 | /// payloads from a channel.
131 | fn get_channel_response(channel: HttpChannel) -> Result> {
132 | Ok(Response::new(Body::wrap_stream(dnstap_receiver_to_stream(
133 | channel,
134 | ))))
135 | }
136 |
137 | /// Read dnstap payloads from the [`async_channel::Receiver`] embedded in an `HttpChannel`,
138 | /// serialize them using the unidirectional Frame Streams encoding, and yield them to a
139 | /// [`tokio_stream::Stream`]. Increments the success counter contained in the `HttpChannel`.
140 | fn dnstap_receiver_to_stream(
141 | channel: HttpChannel,
142 | ) -> impl tokio_stream::Stream- > {
143 | let mut f = FrameStreamsCodec {};
144 |
145 | stream! {
146 | // Write the Start frame with the dnstap content type to the beginning of the stream.
147 | let mut buf = BytesMut::with_capacity(64);
148 | f.encode(
149 | Frame::ControlStart(framestreams_codec::encode_content_type_payload(
150 | b"protobuf:dnstap.Dnstap",
151 | )),
152 | &mut buf,
153 | )?;
154 | yield Ok(buf);
155 |
156 | // Get each dnstap payload from the channel and write it to the stream.
157 | loop {
158 | match channel.receiver.try_recv() {
159 | Ok(d) => {
160 | // Accounting.
161 | channel.success_metric.inc();
162 |
163 | // Get the length of the serialized protobuf.
164 | let len = d.encoded_len();
165 |
166 | // Create a [`BytesMut`] of the exact size needed for this data frame.
167 | let mut buf = BytesMut::with_capacity(4 + len);
168 |
169 | // Write the length of the protobuf to the beginning of the data frame.
170 | buf.put_u32(len as u32);
171 |
172 | // Serialize the protobuf and write it to the data frame.
173 | d.encode(&mut buf).unwrap();
174 |
175 | yield Ok(buf);
176 | }
177 | Err(_) => {
178 | // Write the Stop frame to the end of the stream.
179 | let mut buf = BytesMut::with_capacity(64);
180 | f.encode(Frame::ControlStop, &mut buf)?;
181 | yield Ok(buf);
182 |
183 | // No more frames.
184 | break;
185 | }
186 | }
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/bin/dnstap-replay/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 Fastly, Inc.
2 |
3 | use anyhow::Result;
4 | use async_channel::{bounded, Receiver, Sender};
5 | use clap::{ArgAction, Parser, ValueHint};
6 | use ip_network::IpNetwork;
7 | use log::*;
8 | use std::net::SocketAddr;
9 | use std::path::PathBuf;
10 | use std::sync::atomic::{AtomicBool, Ordering};
11 | use std::sync::Arc;
12 | use tokio::net::UnixListener;
13 |
14 | /// Prometheus metrics.
15 | pub mod metrics;
16 |
17 | /// The dnstap payload processing handler.
18 | mod dnstap_handler;
19 | use dnstap_handler::*;
20 |
21 | /// The Frame Streams connection processing handler.
22 | mod frame_handler;
23 | use frame_handler::*;
24 |
25 | /// The HTTP server for stats and reporting.
26 | mod http_handler;
27 | use http_handler::*;
28 |
29 | /// The status file monitoring handler.
30 | mod monitor_handler;
31 | use monitor_handler::*;
32 |
33 | /// The generated protobuf definitions for the dnstap protocol.
34 | use dnstap_utils::dnstap;
35 |
36 | /// Server configuration and state.
37 | struct Server {
38 | /// Command-line options.
39 | opts: Opts,
40 |
41 | /// Channels.
42 | channels: Channels,
43 | }
44 |
45 | #[derive(Clone)]
46 | pub struct Channels {
47 | /// The send side of the async channel used by [`FrameHandler`]'s to send decoded dnstap
48 | /// protobuf messages to the [`DnstapHandler`]'s.
49 | sender: Sender,
50 |
51 | /// The receive side of the async channel used by [`DnstapHandler`]'s to receive decoded
52 | /// dnstap messages from the [`FrameHandler`]'s.
53 | receiver: Receiver,
54 |
55 | /// The send side of the async channel used by [`DnstapHandler`]'s to send error dnstap
56 | /// protobuf messages to the [`HttpHandler`].
57 | error_sender: Sender,
58 |
59 | /// The receive side of the async channel used by [`DnstapHandler`]'s to send error dnstap
60 | /// protobuf messages to the [`HttpHandler`].
61 | error_receiver: Receiver,
62 |
63 | /// The send side of the async channel used by [`DnstapHandler`]'s to send timeout dnstap
64 | /// protobuf messages to the [`HttpHandler`].
65 | timeout_sender: Sender,
66 |
67 | /// The receive side of the async channel used by [`DnstapHandler`]'s to send timeout dnstap
68 | /// protobuf messages to the [`HttpHandler`].
69 | timeout_receiver: Receiver,
70 | }
71 |
72 | /// Command-line arguments.
73 | #[derive(Parser, Clone)]
74 | pub struct Opts {
75 | /// Capacity of async channel for handler payload distribution
76 | #[clap(long, default_value = "10000")]
77 | channel_capacity: usize,
78 |
79 | /// Capacity of async channel for /errors endpoint buffer
80 | #[clap(long, default_value = "100000")]
81 | channel_error_capacity: usize,
82 |
83 | /// Capacity of async channel for /timeouts endpoint buffer
84 | #[clap(long, default_value = "100000")]
85 | channel_timeout_capacity: usize,
86 |
87 | /// UDP DNS server and port to send queries to
88 | #[clap(long, name = "DNS IP:PORT")]
89 | dns: SocketAddr,
90 |
91 | /// DSCP value to set on outgoing queries
92 | #[clap(long,
93 | name = "DSCP code point",
94 | value_parser = clap::value_parser!(u8).range(0..63))]
95 | dscp: Option,
96 |
97 | /// HTTP server socket to listen on for stats and reporting
98 | #[clap(long, name = "HTTP IP:PORT")]
99 | http: SocketAddr,
100 |
101 | /// Whether to ignore UDP responses with the TC bit set
102 | #[clap(long)]
103 | ignore_tc: bool,
104 |
105 | /// Ignore queries from this IPv4 or IPv6 network
106 | #[clap(long, value_parser = clap::value_parser!(IpNetwork))]
107 | ignore_query_net: Vec,
108 |
109 | /// Number of UDP client sockets to use to send queries to DNS server
110 | #[clap(long, default_value = "10")]
111 | num_sockets: usize,
112 |
113 | /// Whether to add PROXY v2 header to re-sent DNS queries
114 | #[clap(long)]
115 | proxy: bool,
116 |
117 | /// Whether to add timespec TLV to PROXY v2 header
118 | #[clap(long)]
119 | proxy_timespec: bool,
120 |
121 | /// Time to delay after status files match
122 | #[clap(long, name = "MILLISECONDS", default_value = "5000", required = false)]
123 | match_status_delay: u64,
124 |
125 | /// Match status files to compare
126 | #[clap(long = "match-status-files",
127 | name = "STATUS-FILE",
128 | required = false,
129 | num_args(2..),
130 | value_parser,
131 | value_hint = ValueHint::FilePath)
132 | ]
133 | status_files: Vec,
134 |
135 | /// Unix socket path to listen on
136 | #[clap(long, name = "PATH")]
137 | unix: String,
138 |
139 | /// Increase verbosity level
140 | #[clap(short, long, action = ArgAction::Count)]
141 | verbose: u8,
142 | }
143 |
144 | impl Server {
145 | /// Create a new [`Server`] and prepare its state.
146 | pub fn new(opts: &Opts) -> Self {
147 | // Create the channel for connecting [`FrameHandler`]'s and [`DnstapHandler`]'s.
148 | let (sender, receiver) = bounded(opts.channel_capacity);
149 |
150 | // Create the error channel for connecting [`DnstapHandler`]'s and the [`HttpHandler`].
151 | let (error_sender, error_receiver) = bounded(opts.channel_error_capacity);
152 |
153 | // Create the timeout channel for connecting [`DnstapHandler`]'s and the [`HttpHandler`].
154 | let (timeout_sender, timeout_receiver) = bounded(opts.channel_timeout_capacity);
155 |
156 | Server {
157 | opts: opts.clone(),
158 | channels: Channels {
159 | sender,
160 | receiver,
161 | error_sender,
162 | error_receiver,
163 | timeout_sender,
164 | timeout_receiver,
165 | },
166 | }
167 | }
168 |
169 | /// Run the server. Binds a Unix socket listener to the filesystem and listens for incoming
170 | /// connections. Each incoming connection is handled by a newly spawned [`FrameHandler`].
171 | ///
172 | /// Also starts up the [`HttpHandler`] and the number of [`DnstapHandler`]'s specified by the
173 | /// configuration. Each [`DnstapHandler`] creates its own UDP query socket for querying the
174 | /// configured DNS server.
175 | async fn run(&mut self) -> Result<()> {
176 | let match_status = Arc::new(AtomicBool::new(false));
177 |
178 | // Start up the [`MonitorHandler'].
179 | if !self.opts.status_files.is_empty() {
180 | let match_status_mh = match_status.clone();
181 | let mut monitor_handler =
182 | MonitorHandler::new(&self.opts.status_files, self.opts.match_status_delay)?;
183 | tokio::spawn(async move {
184 | if let Err(err) = monitor_handler.run(match_status_mh).await {
185 | error!("Monitor handler error: {}", err);
186 | }
187 | });
188 | } else {
189 | match_status.store(true, Ordering::Relaxed);
190 | crate::metrics::MATCH_STATUS.set(1);
191 | }
192 |
193 | // Start up the [`HttpHandler`].
194 | let http_handler = HttpHandler::new(self.opts.http, &self.channels);
195 | tokio::spawn(async move {
196 | if let Err(err) = http_handler.run().await {
197 | error!("Hyper HTTP server error: {}", err);
198 | }
199 | });
200 |
201 | // Start up the [`DnstapHandler`]'s.
202 | for _ in 0..self.opts.num_sockets {
203 | let match_status_dh = match_status.clone();
204 |
205 | // Create a new [`DnstapHandler`] and give it a cloned channel receiver.
206 | let mut dnstap_handler =
207 | DnstapHandler::new(&self.opts, &self.channels, match_status_dh).await?;
208 |
209 | // Spawn a new task to run the [`DnstapHandler`].
210 | tokio::spawn(async move {
211 | if let Err(err) = dnstap_handler.run().await {
212 | error!("DnstapHandler error: {}", err);
213 | }
214 | });
215 | }
216 | info!(
217 | "Sending DNS queries to server {} using {} UDP query sockets",
218 | &self.opts.dns, self.opts.num_sockets,
219 | );
220 | if self.opts.proxy {
221 | info!("Sending DNS queries with PROXY v2 header");
222 | }
223 | if let Some(dscp) = self.opts.dscp {
224 | info!("Sending DNS queries with DSCP value {}", dscp);
225 | }
226 |
227 | // Bind to the configured Unix socket. Remove the socket file if it exists.
228 | let _ = std::fs::remove_file(&self.opts.unix);
229 | let listener = UnixListener::bind(&self.opts.unix)?;
230 | info!("Listening on Unix socket path {}", &self.opts.unix);
231 |
232 | // Accept incoming connections on the FrameStreams socket.
233 | loop {
234 | match listener.accept().await {
235 | Ok((stream, _addr)) => {
236 | // Create a [`FrameHandler`] for this connection.
237 | let mut frame_handler = FrameHandler::new(stream, self.channels.sender.clone());
238 |
239 | // Spawn a new task to run the [`FrameHandler`].
240 | tokio::spawn(async move {
241 | if let Err(err) = frame_handler.run().await {
242 | warn!("FrameHandler error: {}", err);
243 | }
244 | });
245 | }
246 | Err(err) => {
247 | warn!("Accept error: {}", err);
248 | }
249 | }
250 | }
251 | }
252 | }
253 |
254 | fn main() -> Result<()> {
255 | let opts = Opts::parse();
256 |
257 | stderrlog::new()
258 | .verbosity(opts.verbose as usize)
259 | .module(module_path!())
260 | .init()
261 | .unwrap();
262 |
263 | metrics::initialize_metrics();
264 |
265 | let mut server = Server::new(&opts);
266 |
267 | // Start the Tokio runtime.
268 | tokio::runtime::Builder::new_multi_thread()
269 | .worker_threads(2)
270 | .enable_all()
271 | .build()
272 | .unwrap()
273 | .block_on(async { server.run().await })
274 | }
275 |
--------------------------------------------------------------------------------
/src/bin/dnstap-replay/metrics.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2022 Fastly, Inc.
2 |
3 | use lazy_static::{initialize, lazy_static};
4 | use prometheus::{
5 | opts, register_int_counter, register_int_counter_vec, register_int_gauge, IntCounter, IntGauge,
6 | };
7 | use prometheus_static_metric::{make_static_metric, register_static_int_counter_vec};
8 |
9 | make_static_metric! {
10 | pub struct ChannelErrorRxVec: IntCounter {
11 | "result" => {
12 | success,
13 | },
14 | }
15 |
16 | pub struct ChannelErrorTxVec: IntCounter {
17 | "result" => {
18 | success,
19 | error,
20 | },
21 | }
22 |
23 | pub struct ChannelTimeoutRxVec: IntCounter {
24 | "result" => {
25 | success,
26 | },
27 | }
28 |
29 | pub struct ChannelTimeoutTxVec: IntCounter {
30 | "result" => {
31 | success,
32 | error,
33 | },
34 | }
35 |
36 | pub struct DnsComparisonsVec: IntCounter {
37 | "result" => {
38 | matched,
39 | mismatched,
40 | suppressed,
41 | udp_tc_ignored,
42 | query_net_ignored,
43 | },
44 | }
45 |
46 | pub struct DnsQueriesVec: IntCounter {
47 | "result" => {
48 | success,
49 | error,
50 | timeout,
51 | },
52 | }
53 |
54 | pub struct DnstapPayloadsVec: IntCounter {
55 | "result" => {
56 | success,
57 | error,
58 | },
59 | }
60 |
61 | pub struct DnstapHandlerInternalErrorsVec: IntCounter {
62 | "result" => {
63 | discard_non_udp,
64 | },
65 | }
66 | }
67 |
68 | lazy_static! {
69 | pub static ref CHANNEL_ERROR_RX: ChannelErrorRxVec = register_static_int_counter_vec!(
70 | ChannelErrorRxVec,
71 | "dnstap_replay_channel_error_rx_total",
72 | "Number of error channel receives performed.",
73 | &["result"]
74 | )
75 | .unwrap();
76 | pub static ref CHANNEL_ERROR_TX: ChannelErrorTxVec = register_static_int_counter_vec!(
77 | ChannelErrorTxVec,
78 | "dnstap_replay_channel_error_tx_total",
79 | "Number of error channel sends performed.",
80 | &["result"]
81 | )
82 | .unwrap();
83 | pub static ref CHANNEL_TIMEOUT_RX: ChannelTimeoutRxVec = register_static_int_counter_vec!(
84 | ChannelTimeoutRxVec,
85 | "dnstap_replay_channel_timeout_rx_total",
86 | "Number of timeout channel receives performed.",
87 | &["result"]
88 | )
89 | .unwrap();
90 | pub static ref CHANNEL_TIMEOUT_TX: ChannelTimeoutTxVec = register_static_int_counter_vec!(
91 | ChannelTimeoutTxVec,
92 | "dnstap_replay_channel_timeout_tx_total",
93 | "Number of timeout channel sends performed.",
94 | &["result"]
95 | )
96 | .unwrap();
97 | pub static ref DATA_BYTES: IntCounter = register_int_counter!(opts!(
98 | "dnstap_replay_data_bytes_total",
99 | "Number of Frame Streams data frame bytes processed."
100 | ))
101 | .unwrap();
102 | pub static ref DATA_FRAMES: IntCounter = register_int_counter!(opts!(
103 | "dnstap_replay_data_frames_total",
104 | "Number of Frame Streams data frames processed."
105 | ))
106 | .unwrap();
107 | pub static ref DNS_COMPARISONS: DnsComparisonsVec = register_static_int_counter_vec!(
108 | DnsComparisonsVec,
109 | "dnstap_replay_dns_comparisons_total",
110 | "Number of DNS comparison operations performed.",
111 | &["result"]
112 | )
113 | .unwrap();
114 | pub static ref DNS_QUERIES: DnsQueriesVec = register_static_int_counter_vec!(
115 | DnsQueriesVec,
116 | "dnstap_replay_dns_queries_total",
117 | "Number of DNS re-query operations performed.",
118 | &["result"]
119 | )
120 | .unwrap();
121 | pub static ref DNSTAP_PAYLOADS: DnstapPayloadsVec = register_static_int_counter_vec!(
122 | DnstapPayloadsVec,
123 | "dnstap_replay_dnstap_payloads_total",
124 | "Number of dnstap payloads processed.",
125 | &["result"]
126 | )
127 | .unwrap();
128 | pub static ref DNSTAP_HANDLER_INTERNAL_ERRORS: DnstapHandlerInternalErrorsVec =
129 | register_static_int_counter_vec!(
130 | DnstapHandlerInternalErrorsVec,
131 | "dnstap_replay_dnstap_handler_internal_errors_total",
132 | "Number of internal errors encountered by dnstap handler.",
133 | &["result"]
134 | )
135 | .unwrap();
136 | pub static ref MATCH_STATUS: IntGauge = register_int_gauge!(opts!(
137 | "dnstap_replay_match_status",
138 | "Whether match comparisons are enabled (1) or suppressed (0)."
139 | ))
140 | .unwrap();
141 | }
142 |
143 | pub fn initialize_metrics() {
144 | initialize(&CHANNEL_ERROR_RX);
145 | initialize(&CHANNEL_ERROR_TX);
146 | initialize(&CHANNEL_TIMEOUT_RX);
147 | initialize(&CHANNEL_TIMEOUT_TX);
148 | initialize(&DATA_BYTES);
149 | initialize(&DATA_FRAMES);
150 | initialize(&DNS_COMPARISONS);
151 | initialize(&DNS_QUERIES);
152 | initialize(&DNSTAP_PAYLOADS);
153 | initialize(&DNSTAP_HANDLER_INTERNAL_ERRORS);
154 | }
155 |
--------------------------------------------------------------------------------
/src/bin/dnstap-replay/monitor_handler.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2022 Fastly, Inc.
2 |
3 | use anyhow::{bail, Context, Result};
4 | use futures_util::StreamExt;
5 | use inotify::{Event, Inotify, WatchDescriptor, WatchMask};
6 | use log::*;
7 | use std::ffi::OsString;
8 | use std::path::Path;
9 | use std::path::PathBuf;
10 | use std::sync::atomic::{AtomicBool, Ordering};
11 | use std::sync::Arc;
12 | use tokio::time::{sleep, Duration};
13 |
14 | /// Monitor status files for changes and detect when all status files are identical.
15 | pub struct MonitorHandler {
16 | inotify: Option,
17 | monitors: Vec,
18 | delay: u64,
19 | }
20 |
21 | impl MonitorHandler {
22 | /// Create a new [`MonitorHandler`] that watches the set of status files specified in
23 | /// `monitor_files`.
24 | pub fn new(monitor_files: &[PathBuf], delay: u64) -> Result {
25 | let inotify = Inotify::init().context("Failed to initialize inotify")?;
26 |
27 | let mut monitor = MonitorHandler {
28 | inotify: Some(inotify),
29 | monitors: vec![],
30 | delay,
31 | };
32 |
33 | for path in monitor_files {
34 | monitor.add_watch(path)?;
35 | }
36 |
37 | Ok(monitor)
38 | }
39 |
40 | /// Perform status file monitoring.
41 | pub async fn run(&mut self, status: Arc) -> Result<()> {
42 | let mut buffer = [0; 1024];
43 | let inotify = std::mem::take(&mut self.inotify);
44 | let mut stream = inotify.unwrap().into_event_stream(&mut buffer)?;
45 |
46 | // Perform an initial check of the current status files, if any. Since inotify is
47 | // event-driven, events won't be received for pre-existing status files.
48 | let res = self.check().await;
49 |
50 | // Update the match status flag and metric.
51 | status.store(res, Ordering::Relaxed);
52 | crate::metrics::MATCH_STATUS.set(res as i64);
53 |
54 | while let Some(event_or_error) = stream.next().await {
55 | match event_or_error {
56 | Ok(event) => {
57 | // Handle this inotify event.
58 | self.handle_event(event);
59 |
60 | // And then re-check the status files.
61 | let res = self.check().await;
62 |
63 | // Update the match status flag and metric.
64 | status.store(res, Ordering::Relaxed);
65 | crate::metrics::MATCH_STATUS.set(res as i64);
66 | }
67 | Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => continue,
68 | _ => {
69 | panic!("Error while reading inotify events");
70 | }
71 | }
72 | }
73 |
74 | Ok(())
75 | }
76 |
77 | /// Add a file path to the set of status files to monitor.
78 | fn add_watch>(&mut self, watch_path: P) -> Result<()> {
79 | info!("Monitoring '{}' for changes", watch_path.as_ref().display());
80 | let mut monitor = FileMonitor::new(self.inotify.as_mut().unwrap(), watch_path)?;
81 | monitor.update();
82 | self.monitors.push(monitor);
83 | Ok(())
84 | }
85 |
86 | /// Check if all of the status files being monitored have identical contents.
87 | async fn check(&self) -> bool {
88 | let res = match self.monitors.first() {
89 | Some(first) => self.monitors.iter().all(|item| item == first),
90 | None => false,
91 | };
92 | // Sleep for the configured match delay. This should be set to a large enough value to
93 | // allow pending dnstap payloads to be drained and suppressed.
94 | if res && self.delay > 0 {
95 | sleep(Duration::from_millis(self.delay)).await;
96 | }
97 | debug!("Monitor status: {}", res);
98 | res
99 | }
100 |
101 | /// Handle an inotify event.
102 | fn handle_event(&mut self, event: Event) {
103 | debug!("Handling inotify update: {:?}", &event);
104 | if let Some(name) = event.name {
105 | for m in &mut self.monitors {
106 | // Look up which of the monitors have an [`inotify::WatchDescriptor`] and base
107 | // filename that corresponds to the inotify event being processed.
108 | if m.wd == event.wd && name == m.base_name {
109 | // If a monitor was found, the file that it represents has been updated and its
110 | // contents should be reloaded.
111 | m.update();
112 | }
113 | }
114 | }
115 | }
116 | }
117 |
118 | /// Monitor a small file using inotify. Contains an [`inotify::WatchDescriptor`] which corresponds
119 | /// to the directory containing the file, and caches the file's contents.
120 | #[derive(Debug)]
121 | struct FileMonitor {
122 | // The final directory component of the status file being watched, e.g. "status.txt".
123 | pub base_name: PathBuf,
124 |
125 | // The full path to the file being watched, e.g. "/run/directory/status.txt".
126 | pub full_name: PathBuf,
127 |
128 | // The WatchDescriptor returned by inotify for the directory being watched.
129 | pub wd: WatchDescriptor,
130 |
131 | // The current contents of the status file being watched.
132 | pub contents: Option,
133 | }
134 |
135 | impl Eq for FileMonitor {}
136 |
137 | impl PartialEq for FileMonitor {
138 | fn eq(&self, other: &Self) -> bool {
139 | if self.contents.is_some() && other.contents.is_some() {
140 | self.contents == other.contents
141 | } else {
142 | false
143 | }
144 | }
145 | }
146 |
147 | impl FileMonitor {
148 | /// Create a new [`FileMonitor`] that watches a given filesystem path and add the directory
149 | /// that contains it to an [`Inotify`] to be watched.
150 | pub fn new>(inotify: &mut Inotify, watch_path: P) -> Result {
151 | // Figure out the canonical name of the directory that contains the filesystem path.
152 | let watch_dir = parent_directory_to_monitor_from_filename(&watch_path)?;
153 |
154 | // Extract the final directory component of `watch_path`.
155 | let base_name = match watch_path.as_ref().file_name() {
156 | Some(path) => PathBuf::from(path),
157 | None => bail!(
158 | "Unable to extract base filename from '{}'",
159 | watch_path.as_ref().display()
160 | ),
161 | };
162 |
163 | // Construct the full, canonicalized path name to the file path being monitored.
164 | let full_name = watch_dir.join(&base_name);
165 |
166 | // Watch the directory using inotify.
167 | let wd = inotify.watches().add(
168 | &watch_dir,
169 | WatchMask::CLOSE_WRITE
170 | | WatchMask::DELETE
171 | | WatchMask::MOVED_TO
172 | | WatchMask::MOVED_FROM,
173 | )?;
174 |
175 | let mut fm = FileMonitor {
176 | base_name,
177 | full_name,
178 | wd,
179 | contents: None,
180 | };
181 |
182 | // Perform an initial read of the file contents, if it already exists.
183 | fm.update();
184 |
185 | Ok(fm)
186 | }
187 |
188 | /// Read and cache the contents of the file being monitored, if it exists.
189 | pub fn update(&mut self) {
190 | self.contents = std::fs::read_to_string(&self.full_name).ok();
191 | }
192 | }
193 |
194 | fn parent_directory_to_monitor_from_filename>(path: P) -> Result {
195 | if path.as_ref().is_dir() {
196 | bail!(
197 | "Path '{}' is a directory, not a file",
198 | path.as_ref().display()
199 | );
200 | }
201 | match path.as_ref().parent() {
202 | Some(p) => Ok(p.canonicalize()?),
203 | None => bail!(format!(
204 | "Unable to find parent of '{}'",
205 | path.as_ref().display()
206 | )),
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/bin/fmt-dns-message/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Fastly, Inc.
2 |
3 | use anyhow::Result;
4 | use clap::Parser;
5 | use std::fmt::Debug;
6 | use std::io::Write;
7 |
8 | use dnstap_utils::util::fmt_dns_message;
9 |
10 | #[derive(Parser, Debug)]
11 | struct Opts {
12 | /// Hex-encoded DNS message data to decode
13 | hex_msg_bytes: String,
14 | }
15 |
16 | fn main() -> Result<()> {
17 | let mut opts = Opts::parse();
18 |
19 | // Strip whitespace characters from the command-line input data.
20 | opts.hex_msg_bytes.retain(|c| !c.is_whitespace());
21 |
22 | // Decode the hex-encoded input data into a binary, wire-format DNS message.
23 | let raw_msg_bytes = hex::decode(&opts.hex_msg_bytes)?;
24 |
25 | // Format the wire-format DNS message to a string.
26 | let mut fmt_buffer = String::with_capacity(2048);
27 | fmt_dns_message(&mut fmt_buffer, "", &raw_msg_bytes);
28 |
29 | // Write the formatted DNS message to stdout.
30 | std::io::stdout().write_all(fmt_buffer.as_bytes())?;
31 |
32 | Ok(())
33 | }
34 |
--------------------------------------------------------------------------------
/src/framestreams_codec.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Fastly, Inc.
2 |
3 | use bytes::{Buf, BufMut, BytesMut};
4 | use tokio_util::codec::{Decoder, Encoder};
5 |
6 | // Implementation defined limits.
7 |
8 | pub const CONTROL_FRAME_LENGTH_MAX: usize = 512;
9 | pub const FRAME_LENGTH_MAX: usize = 128 * 1024;
10 |
11 | // Protocol constants.
12 |
13 | pub const FRAMESTREAMS_CONTROL_ACCEPT: u32 = 0x01;
14 | pub const FRAMESTREAMS_CONTROL_START: u32 = 0x02;
15 | pub const FRAMESTREAMS_CONTROL_STOP: u32 = 0x03;
16 | pub const FRAMESTREAMS_CONTROL_READY: u32 = 0x04;
17 | pub const FRAMESTREAMS_CONTROL_FINISH: u32 = 0x05;
18 |
19 | pub const FRAMESTREAMS_CONTROL_FIELD_CONTENT_TYPE: u32 = 0x01;
20 |
21 | pub const FRAMESTREAMS_ESCAPE_SEQUENCE: u32 = 0x00;
22 |
23 | #[derive(Debug)]
24 | pub enum Frame {
25 | ControlReady(BytesMut),
26 | ControlAccept(BytesMut),
27 | ControlStart(BytesMut),
28 | ControlStop,
29 | ControlFinish,
30 | ControlUnknown(BytesMut),
31 | Data(BytesMut),
32 | }
33 |
34 | pub struct FrameStreamsCodec {}
35 |
36 | impl Encoder for FrameStreamsCodec {
37 | type Error = std::io::Error;
38 |
39 | fn encode(&mut self, frame: Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
40 | match frame {
41 | Frame::ControlReady(payload) => {
42 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE);
43 | dst.put_u32(4);
44 | dst.put_u32(FRAMESTREAMS_CONTROL_READY);
45 | dst.put(payload);
46 | }
47 | Frame::ControlAccept(payload) => {
48 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE);
49 | dst.put_u32(4 + payload.len() as u32);
50 | dst.put_u32(FRAMESTREAMS_CONTROL_ACCEPT);
51 | dst.put(payload);
52 | }
53 | Frame::ControlStart(payload) => {
54 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE);
55 | dst.put_u32(4 + payload.len() as u32);
56 | dst.put_u32(FRAMESTREAMS_CONTROL_START);
57 | dst.put(payload);
58 | }
59 | Frame::ControlStop => {
60 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE);
61 | dst.put_u32(4);
62 | dst.put_u32(FRAMESTREAMS_CONTROL_STOP);
63 | }
64 | Frame::ControlFinish => {
65 | dst.put_u32(FRAMESTREAMS_ESCAPE_SEQUENCE);
66 | dst.put_u32(4);
67 | dst.put_u32(FRAMESTREAMS_CONTROL_FINISH);
68 | }
69 | Frame::ControlUnknown(_) => todo!(),
70 | Frame::Data(payload) => {
71 | dst.put_u32(payload.len() as u32);
72 | dst.put(payload);
73 | }
74 | }
75 | Ok(())
76 | }
77 | }
78 |
79 | impl Decoder for FrameStreamsCodec {
80 | type Item = Frame;
81 | type Error = std::io::Error;
82 |
83 | fn decode(&mut self, src: &mut BytesMut) -> Result